覆盖

对持续交付的赞美

Praise for Continuous Delivery

“如果您需要更频繁地部署软件,这本书适合您。应用它将帮助您降低风险、消除繁琐的工作并增强信心。我将在我当前的所有项目中使用这里的原则和实践。”

“If you need to deploy software more frequently, this book is for you. Applying it will help you reduce risk, eliminate tedious work, and increase confidence. I’ll be using the principles and practices here on all my current projects.”

Kent Beck,三河研究所

Kent Beck, Three Rivers Institute

“无论您的软件开发团队是否已经了解持续集成与源代码控制一样必要,这都是必读的。本书的独特之处在于将整个开发和交付过程结合在一起,提供了哲学和原则,而不仅仅是技术和工具。作者使广泛的受众可以访问从测试自动化到自动化部署的主题。开发团队中的每个人,包括程序员、测试人员、系统管理员、DBA 和经理,都需要阅读这本书。”

“Whether or not your software development team already understands that continuous integration is every bit as necessary as source code control, this is required reading. This book is unique in tying the whole development and delivery process together, providing a philosophy and principles, not just techniques and tools. The authors make topics from test automation to automated deployment accessible to a wide audience. Everyone on a development team, including programmers, testers, system administrators, DBAs, and managers, needs to read this book.”

— Lisa Crispin,敏捷测试的合著者

Lisa Crispin, co-author of Agile Testing

“对于许多组织而言,持续交付不仅仅是一种部署方法,它对开展业务至关重要。这本书向您展示了如何在您的环境中使持续交付成为有效的现实。”

“For many organizations Continuous Delivery isn’t just a deployment methodology, it’s critical to doing business. This book shows you how to make Continuous Delivery an effective reality in your environment.”

——詹姆斯·特恩布尔,《用木偶牵线》的作者

James Turnbull, author of Pulling Strings with Puppet

“一本清晰、准确、写得很好的书,让读者了解对发布过程的期望。作者逐步说明了软件部署的期望和障碍。这本书是任何软件工程师图书馆的必需品。”

“A clear, precise, well-written book that gives readers an idea of what to expect for the release process. The authors give a step-by-step account of expectations and hurdles for software deployment. This book is a necessity for any software engineer’s library.”

Leyna Cotran,加州大学欧文分校软件研究所

Leyna Cotran, Institute for Software Research, University of California, Irvine

“Humble 和 Farley 说明了快速增长的 Web 应用程序成功的原因。持续部署和交付已经从有争议变成了司空见惯,本书对它进行了很好的介绍。它确实是开发和运营在许多层面上的交集,而这些人做到了。”

“Humble and Farley illustrates what makes fast-growing web applications successful. Continuous deployment and delivery has gone from controversial to commonplace and this book covers it excellently. It’s truly the intersection of development and operations on many levels, and these guys nailed it.”

John Allspaw,Etsy.com 技术运营副总裁,《容量规划网络运营的艺术》一书的作者

John Allspaw, VP Technical Operations, Etsy.com and author of The Art of Capacity Planning and Web Operations

“如果您从事构建和交付基于软件的服务的业务,那么您将很好地内化持续交付中如此清楚解释的概念。但除了概念之外,Humble 和 Farley 还提供了一本出色的剧本,可以快速可靠地交付变革。”

“If you are in the business of building and delivering a software-based service, you would be well served to internalize the concepts that are so clearly explained in Continuous Delivery. But going beyond just the concepts, Humble and Farley provide an excellent playbook for rapidly and reliably delivering change.”

Damon Edwards,DTO Solutions 总裁兼 dev2ops.org 联合编辑

Damon Edwards, President of DTO Solutions and co-editor of dev2ops.org

“我相信任何处理软件发布的人都能够拿起这本书,翻到任何章节并快速获得有价值的信息;或者从头到尾阅读本书,并能够以对他们的组织有意义的方式简化他们的构建和部署过程。在我看来,这是构建、部署、测试和发布软件的必备手册。”

“I believe that anyone who deals with software releases would be able to pick up this book, go to any chapter and quickly get valuable information; or read the book from cover to cover and be able to streamline their build and deploy process in a way that makes sense for their organization. In my opinion, this is an essential handbook for building, deploying, testing, and releasing software.”

Sarah Edrie,哈佛商学院质量工程总监

Sarah Edrie, Director of Quality Engineering, Harvard Business School

“持续交付是任何现代软件团队持续集成之后合乎逻辑的下一步。这本书的目标是公认的雄心勃勃的目标,即不断向客户提供有价值的软件,并通过一套清晰、有效的原则和实践使这一目标成为可能。”

“Continuous Delivery is the logical next step after Continuous Integration for any modern software team. This book takes the admittedly ambitous goal of constantly delivering valuable software to customers, and makes it achievable through a set of clear, effective principles and practices.”

Rob Sanheim,Relevance, Inc. 负责

Rob Sanheim, Principal at Relevance, Inc.

持续交付

Continuous Delivery

杰斯谦卑和大卫法利

Jez Humble and David Farley

图片

新泽西州上马鞍河 • 波士顿 • 印第安纳波利斯 • 旧金山

纽约 • 多伦多 • 蒙特利尔 • 伦敦 • 慕尼黑 • 巴黎 • 马德里

开普敦 • 悉尼 • 东京 • 新加坡 • 墨西哥城



Upper Saddle River, NJ • Boston • Indianapolis • San Francisco

New York • Toronto • Montreal • London • Munich • Paris • Madrid

Cape Town • Sydney • Tokyo • Singapore • Mexico City

制造商和销售商用来区分其产品的许多名称都被声明为商标。如果这些名称出现在本书中,并且出版商知道商标声明,则这些名称的首字母大写或全部大写。

Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in this book, and the publisher was aware of a trademark claim, the designations have been printed with initial capital letters or in all capitals.

美国公司和政府销售

(800) 382–3419

corpsales@pearsontechgroup.com

U.S. Corporate and Government Sales

(800) 382–3419

corpsales@pearsontechgroup.com

国际销售

international@pearson.com

International Sales

international@pearson.com

持续交付:通过构建、测试和部署自动化发布可靠的软件/Jez Humble,David Farley。

    p. 厘米。

Continuous delivery : reliable software releases through build, test, and deployment automation / Jez Humble, David Farley.

    p. cm.

包括参考书目和索引。

Includes bibliographical references and index.

ISBN 978-0-321-60191-9(精装本:alk.paper) 1. 计算机软件——开发。2. 计算机软件——可靠性。3.计算机软件--测试。I. Farley, David, 1959-II。标题。

ISBN 978-0-321-60191-9 (hardback : alk. paper) 1. Computer software--Development. 2. Computer software--Reliability. 3. Computer software--Testing. I. Farley, David, 1959-II. Title.

QA76.76.D47H843 2010

QA76.76.D47H843 2010

005.1--dc22

005.1--dc22

                                                          2010022186

                                                          2010022186

Pearson Education, Inc

Rights and Contracts Department

501 Boylston Street, Suite 900

Boston, MA 02116

传真 (617) 671 3447

Pearson Education, Inc

Rights and Contracts Department

501 Boylston Street, Suite 900

Boston, MA 02116

Fax (617) 671 3447

谨以此书献给我的父亲,他一直给予我无条件的爱和支持

——杰兹

This book is dedicated to my dad, who has always given me his unconditional love and support.

—Jez

这本书献给我的父亲,他总是给我指明正确的方向

——戴夫

This book is dedicated to my dad, who always showed me the right direction.

—Dave

内容

Contents

前言

Foreword

前言

Preface

致谢

Acknowledgments

关于作者

About the Authors

第一部分:基础

Part I: Foundations

第 1 章:交付软件的问题

Chapter 1: The Problem of Delivering Software

介绍

Introduction

一些常见的发布反模式

Some Common Release Antipatterns

反模式:手动部署软件

Antipattern: Deploying Software Manually

反模式:仅在开发完成后部署到类生产环境

Antipattern: Deploying to a Production-like Environment Only after Development Is Complete

反模式:生产环境的手动配置管理

Antipattern: Manual Configuration Management of Production Environments

我们能做得更好吗?

Can We Do Better?

我们如何实现我们的目标?

How Do We Achieve Our Goal?

每一个变化都应该触发反馈过程

Every Change Should Trigger the Feedback Process

必须尽快收到反馈

The Feedback Must Be Received as Soon as Possible

交付团队必须收到反馈然后采取行动

The Delivery Team Must Receive Feedback and Then Act on It

这个过程有规模吗?

Does This Process Scale?

有什么好处?

What Are the Benefits?

赋能团队

Empowering Teams

减少错误

Reducing Errors

减轻压力

Lowering Stress

部署灵活性

Deployment Flexibility

熟能生巧

Practice Makes Perfect

发布候选

The Release Candidate

每次签到都会导致潜在的释放

Every Check-in Leads to a Potential Release

软件交付原则

Principles of Software Delivery

为发布软件创建可重复、可靠的流程

Create a Repeatable, Reliable Process for Releasing Software

自动化几乎一切

Automate Almost Everything

将所有内容保存在版本控制中

Keep Everything in Version Control

如果它痛,就更频繁地做,并将疼痛提前

If It Hurts, Do It More Frequently, and Bring the Pain Forward

建立质量

Build Quality In

完成意味着发布

Done Means Released

交付过程人人有责

Everybody Is Responsible for the Delivery Process

连续的提高

Continuous Improvement

概括

Summary

第 2 章:配置管理

Chapter 2: Configuration Management

介绍

Introduction

使用版本控制

Using Version Control

将所有内容绝对保留在版本控制中

Keep Absolutely Everything in Version Control

定期检查到主干

Check In Regularly to Trunk

使用有意义的提交消息

Use Meaningful Commit Messages

管理依赖关系

Managing Dependencies

管理外部库

Managing External Libraries

管理组件

Managing Components

管理软件配置

Managing Software Configuration

配置和灵活性

Configuration and Flexibility

配置类型

Types of Configuration

管理应用程序配置

Managing Application Configuration

跨应用程序管理配置

Managing Configuration across Applications

管理应用程序配置的原则

Principles of Managing Application Configuration

管理您的环境

Managing Your Environments

管理环境的工具

Tools to Manage Environments

管理变革过程

Managing the Change Process

概括

Summary

第 3 章:持续集成

Chapter 3: Continuous Integration

介绍

Introduction

实施持续集成

Implementing Continuous Integration

开始之前你需要什么

What You Need Before You Start

一个基本的持续集成系统

A Basic Continuous Integration System

持续集成的先决条件

Prerequisites for Continuous Integration

定期入住

Check In Regularly

创建一个全面的自动化测试套件

Create a Comprehensive Automated Test Suite

保持构建和测试过程简短

Keep the Build and Test Process Short

管理您的开发工作区

Managing Your Development Workspace

使用持续集成软件

Using Continuous Integration Software

基本操作

Basic Operation

钟声和口哨声

Bells and Whistles

基本实践

Essential Practices

不要签入损坏的构建

Don’t Check In on a Broken Build

在提交之前总是在本地运行所有提交测试,或者让你的 CI 服务器为你做这件事

Always Run All Commit Tests Locally before Committing, or Get Your CI Server to Do It for You

在继续之前等待提交测试通过

Wait for Commit Tests to Pass before Moving On

永远不要在破损的建筑上回家

Never Go Home on a Broken Build

随时准备恢复到以前的版本

Always Be Prepared to Revert to the Previous Revision

恢复前的时间盒修复

Time-Box Fixing before Reverting

不要注释掉失败的测试

Don’t Comment Out Failing Tests

对因您的更改而导致的所有损坏负责

Take Responsibility for All Breakages That Result from Your Changes

测试驱动开发

Test-Driven Development

建议的做法

Suggested Practices

极限编程(XP)开发实践

Extreme Programming (XP) Development Practices

因架构漏洞而导致构建失败

Failing a Build for Architectural Breaches

构建慢速测试失败

Failing the Build for Slow Tests

因警告和代码风格违规而导致构建失败

Failing the Build for Warnings and Code Style Breaches

分布式团队

Distributed Teams

对过程的影响

The Impact on Process

集中持续集成

Centralized Continuous Integration

技术问题

Technical Issues

替代方法

Alternative Approaches

分布式版本控制系统

Distributed Version Control Systems

概括

Summary

第 4 章:实施测试策略

Chapter 4: Implementing a Testing Strategy

介绍

Introduction

测试类型

Types of Tests

支持开发过程的面向业务的测试

Business-Facing Tests That Support the Development Process

支持开发过程的面向技术的测试

Technology-Facing Tests That Support the Development Process

批评项目的面向业务的测试

Business-Facing Tests That Critique the Project

批评项目的面向技术的测试

Technology-Facing Tests That Critique the Project

测试双打

Test Doubles

现实生活中的情况和策略

Real-Life Situations and Strategies

新项目

New Projects

中期项目

Midproject

遗留系统

Legacy Systems

集成测试

Integration Testing

过程

Process

管理缺陷积压

Managing Defect Backlogs

概括

Summary

第二部分:部署管道

Part II: The Deployment Pipeline

第 5 章:部署管道剖析

Chapter 5: Anatomy of the Deployment Pipeline

介绍

Introduction

什么是部署管道?

What Is a Deployment Pipeline?

基本部署管道

A Basic Deployment Pipeline

部署管道实践

Deployment Pipeline Practices

只构建一次二进制文件

Only Build Your Binaries Once

以相同的方式部署到每个环境

Deploy the Same Way to Every Environment

对您的部署进​​行冒烟测试

Smoke-Test Your Deployments

部署到生产副本

Deploy into a Copy of Production

每个更改都应立即通过管道传播

Each Change Should Propagate through the Pipeline Instantly

如果管道的任何部分出现故障,请停止该管道

If Any Part of the Pipeline Fails, Stop the Line

提交阶段

The Commit Stage

提交阶段最佳实践

Commit Stage Best Practices

自动验收测试门

The Automated Acceptance Test Gate

自动验收测试最佳实践

Automated Acceptance Test Best Practices

后续测试阶段

Subsequent Test Stages

手动测试

Manual Testing

非功能测试

Nonfunctional Testing

准备发布

Preparing to Release

自动化部署和发布

Automating Deployment and Release

取消更改

Backing Out Changes

以成功为基础

Building on Success

实施部署管道

Implementing a Deployment Pipeline

为您的价值流建模并创建行走骨架

Modeling Your Value Stream and Creating a Walking Skeleton

自动化构建和部署过程

Automating the Build and Deployment Process

自动化单元测试和代码分析

Automating the Unit Tests and Code Analysis

自动化验收测试

Automating Acceptance Tests

发展你的管道

Evolving Your Pipeline

指标

Metrics

概括

Summary

第 6 章:构建和部署脚本

Chapter 6: Build and Deployment Scripting

介绍

Introduction

构建工具概述

An Overview of Build Tools

制作

Make

蚂蚁

Ant

NAnt 和 MSBuild

NAnt and MSBuild

行家

Maven

Rake

建设者

Buildr

圣杯

Psake

构建和部署脚本的原则和实践

Principles and Practices of Build and Deployment Scripting

为部署管道中的每个阶段创建脚本

Create a Script for Each Stage in Your Deployment Pipeline

使用适当的技术来部署您的应用程序

Use an Appropriate Technology to Deploy Your Application

使用相同的脚本部署到每个环境

Use the Same Scripts to Deploy to Every Environment

使用操作系统的打包工具

Use Your Operating System’s Packaging Tools

确保部署过程是幂等的

Ensure the Deployment Process Is Idempotent

逐步发展您的部署系统

Evolve Your Deployment System Incrementally

面向 JVM 的应用程序的项目结构

Project Structure for Applications That Target the JVM

项目布局

Project Layout

部署脚本

Deployment Scripting

部署和测试层

Deploying and Testing Layers

测试您的环境配置

Testing Your Environment’s Configuration

技巧和窍门

Tips and Tricks

始终使用相对路径

Always Use Relative Paths

消除手动步骤

Eliminate Manual Steps

内置从二进制文件到版本控制的可追溯性

Build In Traceability from Binaries to Version Control

不要将二进制文件作为构建的一部分检入版本控制

Don’t Check Binaries into Version Control as Part of Your Build

测试目标不应使构建失败

Test Targets Should Not Fail the Build

通过集成冒烟测试约束您的应用程序

Constrain Your Application with Integrated Smoke Tests

.NET 提示和技巧

.NET Tips and Tricks

概括

Summary

第 7 章:提交阶段

Chapter 7: The Commit Stage

介绍

Introduction

提交阶段的原则和实践

Commit Stage Principles and Practices

提供快速、有用的反馈

Provide Fast, Useful Feedback

什么应该打破提交阶段?

What Should Break the Commit Stage?

小心照料提交阶段

Tend the Commit Stage Carefully

赋予开发者所有权

Give Developers Ownership

为超大型团队使用 Build Master

Use a Build Master for Very Large Teams

提交阶段的结果

The Results of the Commit Stage

工件存储库

The Artifact Repository

提交测试套件原则和实践

Commit Test Suite Principles and Practices

避免用户界面

Avoid the User Interface

使用依赖注入

Use Dependency Injection

避免数据库

Avoid the Database

避免单元测试中的异步

Avoid Asynchrony in Unit Tests

使用测试替身

Using Test Doubles

最小化测试中的状态

Minimizing State in Tests

假装时间

Faking Time

蛮力

Brute Force

概括

Summary

第 8 章:自动化验收测试

Chapter 8: Automated Acceptance Testing

介绍

Introduction

为什么自动化验收测试必不可少?

Why Is Automated Acceptance Testing Essential?

如何创建可维护的验收测试套件

How to Create Maintainable Acceptance Test Suites

针对 GUI 进行测试

Testing against the GUI

创建验收测试

Creating Acceptance Tests

分析师和测试人员的角色

The Role of Analysts and Testers

迭代项目分析

Analysis on Iterative Projects

作为可执行规范的验收标准

Acceptance Criteria as Executable Specifications

应用驱动层

The Application Driver Layer

如何表达你的接受标准

How to Express Your Acceptance Criteria

窗口驱动程序模式:将测试与 GUI 解耦

The Window Driver Pattern: Decoupling the Tests from the GUI

实施验收测试

Implementing Acceptance Tests

验收测试中的状态

State in Acceptance Tests

流程边界、封装和测试

Process Boundaries, Encapsulation, and Testing

管理异步和超时

Managing Asynchrony and Timeouts

使用测试替身

Using Test Doubles

验收测试阶段

The Acceptance Test Stage

保持验收测试绿色

Keeping Acceptance Tests Green

部署测试

Deployment Tests

验收测试性能

Acceptance Test Performance

重构常见任务

Refactor Common Tasks

共享昂贵的资源

Share Expensive Resources

并行测试

Parallel Testing

使用计算网格

Using Compute Grids

概括

Summary

第 9 章:测试非功能性需求

Chapter 9: Testing Nonfunctional Requirements

介绍

Introduction

管理非功能性需求

Managing Nonfunctional Requirements

分析非功能性需求

Analyzing Nonfunctional Requirements

容量编程

Programming for Capacity

测量容量

Measuring Capacity

如何定义能力测试的成功与失败?

How Should Success and Failure Be Defined for Capacity Tests?

容量测试环境

The Capacity-Testing Environment

自动化容量测试

Automating Capacity Testing

通过用户界面进行容量测试

Capacity Testing via the User Interface

记录针对服务或公共 API 的交互

Recording Interactions against a Service or Public API

使用录制的交互模板

Using Recorded Interaction Templates

使用容量测试存根开发测试

Using Capacity Test Stubs to Develop Tests

将容量测试添加到部署管道

Adding Capacity Tests to the Deployment Pipeline

容量测试系统的其他好处

Additional Benefits of a Capacity Test System

概括

Summary

第 10 章:部署和发布应用程序

Chapter 10: Deploying and Releasing Applications

介绍

Introduction

创建发布策略

Creating a Release Strategy

发布计划

The Release Plan

发布产品

Releasing Products

部署和推广您的应用程序

Deploying and Promoting Your Application

第一次部署

The First Deployment

对您的发布过程建模并促进构建

Modeling Your Release Process and Promoting Builds

提升配置

Promoting Configuration

编排

Orchestration

部署到暂存环境

Deployments to Staging Environments

回滚部署和零停机发布

Rolling Back Deployments and Zero-Downtime Releases

通过重新部署以前的好版本回滚

Rolling Back by Redeploying the Previous Good Version

零停机发布

Zero-Downtime Releases

蓝绿部署

Blue-Green Deployments

金丝雀发布

Canary Releasing

紧急修复

Emergency Fixes

持续部署

Continuous Deployment

持续发布用户安装的软件

Continuously Releasing User-Installed Software

技巧和窍门

Tips and Tricks

执行部署的人员应参与创建部署过程

The People Who Do the Deployment Should Be Involved in Creating the Deployment Process

记录部署活动

Log Deployment Activities

不要删除旧文件,移动它们

Don’t Delete the Old Files, Move Them

部署是整个团队的责任

Deployment Is the Whole Team’s Responsibility

服务器应用程序不应该有 GUI

Server Applications Should Not Have GUIs

为新部署预热

Have a Warm-Up Period for a New Deployment

快速失败

Fail Fast

不要直接在生产环境上进行更改

Don’t Make Changes Directly on the Production Environment

概括

Summary

第三部分:交付生态系统

Part III: The Delivery Ecosystem

第 11 章:管理基础设施和环境

Chapter 11: Managing Infrastructure and Environments

介绍

Introduction

了解运营团队的需求

Understanding the Needs of the Operations Team

文件和审计

Documentation and Auditing

异常事件警报

Alerts for Abnormal Events

IT 服务连续性规划

IT Service Continuity Planning

使用运营团队熟悉的技术

Use the Technology the Operations Team Is Familiar With

建模和管理基础设施

Modeling and Managing Infrastructure

控制对基础设施的访问

Controlling Access to Your Infrastructure

改变基础设施

Making Changes to Infrastructure

管理服务器供应和配置

Managing Server Provisioning and Configuration

配置服务器

Provisioning Servers

服务器的持续管理

Ongoing Management of Servers

管理中间件的配置

Managing the Configuration of Middleware

管理配置

Managing Configuration

研究产品

Research the Product

检查你的中间件如何处理状态

Examine How Your Middleware Handles State

寻找配置 API

Look for a Configuration API

使用更好的技术

Use a Better Technology

管理基础设施服务

Managing Infrastructure Services

多宿主系统

Multihomed Systems

虚拟化

Virtualization

管理虚拟环境

Managing Virtual Environments

虚拟环境和部署管道

Virtual Environments and the Deployment Pipeline

使用虚拟环境进行高度并行测试

Highly Parallel Testing with Virtual Environments

云计算

Cloud Computing

云中的基础设施

Infrastructure in the Cloud

云端平台

Platforms in the Cloud

一种尺寸不一定适合所有人

One Size Doesn’t Have to Fit All

对云计算的批评

Criticisms of Cloud Computing

监控基础设施和应用程序

Monitoring Infrastructure and Applications

收集数据

Collecting Data

记录

Logging

创建仪表板

Creating Dashboards

行为驱动监控

Behavior-Driven Monitoring

概括

Summary

第 12 章:管理数据

Chapter 12: Managing Data

介绍

Introduction

数据库脚本

Database Scripting

初始化数据库

Initializing Databases

增量变化

Incremental Change

对数据库进行版本控制

Versioning Your Database

管理精心策划的变化

Managing Orchestrated Changes

回滚数据库和零停机发布

Rolling Back Databases and Zero-Downtime Releases

回滚而不丢失数据

Rolling Back without Losing Data

将应用程序部署与数据库迁移解耦

Decoupling Application Deployment from Database Migration

管理测试数据

Managing Test Data

为单元测试伪造数据库

Faking the Database for Unit Tests

管理测试和数据之间的耦合

Managing the Coupling between Tests and Data

测试隔离

Test Isolation

安装和拆卸

Setup and Tear Down

相干测试场景

Coherent Test Scenarios

数据管理和部署管道

Data Management and the Deployment Pipeline

提交阶段测试中的数据

Data in Commit Stage Tests

验收测试中的数据

Data in Acceptance Tests

容量测试数据

Data in Capacity Tests

其他测试阶段的数据

Data in Other Test Stages

概括

Summary

第 13 章:管理组件和依赖关系

Chapter 13: Managing Components and Dependencies

介绍

Introduction

保持您的应用程序可发布

Keeping Your Application Releasable

隐藏新功能直到完成

Hide New Functionality Until It Is Finished

增量地进行所有更改

Make All Changes Incrementally

抽象分支

Branch by Abstraction

依赖关系

Dependencies

依赖地狱

Dependency Hell

管理图书馆

Managing Libraries

组件

Components

如何将代码库划分为组件

How to Divide a Codebase into Components

流水线组件

Pipelining Components

集成管道

The Integration Pipeline

管理依赖图

Managing Dependency Graphs

构建依赖关系图

Building Dependency Graphs

流水线依赖图

Pipelining Dependency Graphs

我们应该什么时候触发构建?

When Should We Trigger Builds?

谨慎乐观

Cautious Optimism

循环依赖

Circular Dependencies

管理二进制文件

Managing Binaries

工件存储库应该如何工作

How an Artifact Repository Should Work

您的部署管道应如何与工件存储库交互

How Your Deployment Pipeline Should Interact with the Artifact Repository

使用 Maven 管理依赖关系

Managing Dependencies with Maven

Maven 依赖重构

Maven Dependency Refactorings

概括

Summary

第 14 章:高级版本控制

Chapter 14: Advanced Version Control

介绍

Introduction

版本控制简史

A Brief History of Revision Control

简历

CVS

颠覆

Subversion

商业版本控制系统

Commercial Version Control Systems

关闭悲观锁定

Switch Off Pessimistic Locking

分支与合并

Branching and Merging

合并

Merging

分支、流和持续集成

Branches, Streams, and Continuous Integration

分布式版本控制系统

Distributed Version Control Systems

什么是分布式版本控制系统?

What Is a Distributed Version Control System?

分布式版本控制系统简史

A Brief History of Distributed Version Control Systems

企业环境中的分布式版本控制系统

Distributed Version Control Systems in Corporate Environments

使用分布式版本控制系统

Using Distributed Version Control Systems

基于流的版本控制系统

Stream-Based Version Control Systems

什么是基于流的版本控制系统?

What Is a Stream-Based Version Control System?

使用流的开发模型

Development Models with Streams

静态和动态视图

Static and Dynamic Views

与基于流的版本控制系统的持续集成

Continuous Integration with Stream-Based Version Control Systems

在主线上开发

Develop on Mainline

在没有分支的情况下进行复杂的更改

Making Complex Changes without Branching

发布分支

Branch for Release

按功能分支

Branch by Feature

按团队划分

Branch by Team

概括

Summary

第 15 章:管理持续交付

Chapter 15: Managing Continuous Delivery

介绍

Introduction

配置和发布管理的成熟度模型

A Maturity Model for Configuration and Release Management

如何使用成熟度模型

How to Use the Maturity Model

项目生命周期

Project Lifecycle

鉴别

Identification

开端

Inception

引发

Initiation

开发和发布

Develop and Release

手术

Operation

风险管理流程

A Risk Management Process

风险管理 101

Risk Management 101

风险管理时间表

Risk Management Timeline

如何进行风险管理练习

How to Do a Risk-Management Exercise

常见的分娩问题——症状和原因

Common Delivery Problems—Their Symptoms and Causes

不频繁或错误的部署

Infrequent or Buggy Deployments

申请质量差

Poor Application Quality

管理不善的持续集成过程

Poorly Managed Continuous Integration Process

配置管理不善

Poor Configuration Management

合规与审计

Compliance and Auditing

文档自动化

Automation over Documentation

加强可追溯性

Enforcing Traceability

在孤岛中工作

Working in Silos

更换管理层

Change Management

概括

Summary

参考书目

Bibliography

指数

Index

马丁福勒的前言

Foreword by Martin Fowler

90 年代后期,我拜访了 Kent Beck,当时他在瑞士的一家保险公司工作。他向我展示了他的项目,他的纪律严明的团队的有趣方面之一是他们每晚都将软件部署到生产环境中。这种定期部署给他们带来了许多优势:编写的软件不会在部署之前无用地等待,他们可以快速响应问题和机会,快速周转导致他们、他们的业务客户和他们的最终客户之间的关系更加深入.

In the late 90s, I paid a visit to Kent Beck, then working in Switzerland for an insurance company. He showed me around his project, and one of the interesting aspects of his highly disciplined team was the fact that they deployed their software into production every night. This regular deployment gave them many advantages: Written software wasn’t waiting uselessly until it was deployed, they could respond quickly to problems and opportunities, and the rapid turnaround led to a much deeper relationship between them, their business customer, and their final customers.

在过去的十年里,我在 ThoughtWorks 工作,我们项目的一个共同主题是缩短想法和可用软件之间的周期时间。我看到了很多项目故事,几乎所有故事都涉及到坚决缩短该周期。虽然我们通常不会每天将产品交付到生产中,但现在看到团队每两周发布一次是很常见的。

In the last decade I’ve worked at ThoughtWorks, and a common theme of our projects has been reducing the cycle time between an idea and usable software. I see plenty of project stories, and almost all involve a determined shortening of that cycle. While we don’t usually do daily deliveries into production, it’s now common to see teams doing bi-weekly releases.

Dave 和 Jez 参与了这场巨变,积极参与了建立频繁、可靠交付文化的项目。他们和我们的同事已经将难以每年部署一次软件的组织带入持续交付的世界,在那里发布成为例行公事。

Dave and Jez have been part of that sea change, actively involved in projects that have built a culture of frequent, reliable deliveries. They and our colleagues have taken organizations that struggled to deploy software once a year into the world of Continuous Delivery, where releasing becomes routine.

该方法的基础,至少对于开发团队而言,是持续集成 (CI)。CI 使整个开发团队保持同步,消除了由于集成问题造成的延迟。几年前,Paul Duvall 在这个系列中写了一本关于 CI 的书。但 CI 只是第一步。已成功集成到主线代码流中的软件仍然不是在生产中发挥作用的软件。Dave 和 Jez 的书从 CI 中续写故事来处理“最后一英里”,描述了如何构建将集成代码转化为生产软件的部署管道。

The foundation for the approach, at least for the development team, is Continuous Integration (CI). CI keeps the entire development team in sync, removing the delays due to integration issues. A couple of years ago, Paul Duvall wrote a book on CI in this series. But CI is just the first step. Software that’s been successfully integrated into a mainline code stream still isn’t software that’s out in production doing its job. Dave and Jez’s book pick up the story from CI to deal with that “last mile,” describing how to build the deployment pipeline that turns integrated code into production software.

这种交付思维长期以来一直是软件开发的一个被遗忘的角落,陷入了开发人员和运营团队之间的一个空洞。因此,本书中的技术基于将这些团队聚集在一起也就不足为奇了——这是新生但不断发展的 DevOps 运动的先兆。此过程还涉及测试人员,因为测试是确保无错误发布的关键要素。贯穿所有这些是高度自动化的,因此可以快速无误地完成工作。

This kind of delivery thinking has long been a forgotten corner of software development, falling into a hole between developers and operations teams. So it’s no surprise that the techniques in this book rest upon bringing these teams together—a harbinger of the nascent but growing DevOps movement. This process also involves testers, as testing is a key element of ensuring error-free releases. Threading through all this is a high degree of automation, so things can be done quickly and without error.

让所有这些工作都需要付出努力,但好处是深远的。长时间、高强度的发布已成为过去。软件客户看到想法迅速变成了他们每天都可以使用的工作代码。也许最重要的是,我们消除了软件开发中有害压力的最大来源之一。没有人喜欢那些试图在周一黎明前发布系统升级的紧张周末。

Getting all this working takes effort, but benefits are profound. Long, high-intensity releases become a thing of the past. Customers of software see ideas rapidly turn into working code that they can use every day. Perhaps most importantly, we remove one of the biggest sources of baleful stress in software development. Nobody likes those tense weekends trying to get a system upgrade released before Monday dawns.

在我看来,一本可以向您展示如何频繁地交付软件并且没有通常的压力的书是不费吹灰之力就可以阅读的。为了你的团队,我希望你同意。

It seems to me that a book that can show you how to deliver your software frequently and without the usual stresses is a no-brainer to read. For your team’s sake, I hope you agree.

前言

Preface

介绍

Introduction

昨天,您的老板要求您向客户展示您系统的强大新功能,但您无法向他们展示任何东西。您所有的开发人员都在开发新功能,他们中没有人可以立即运行该应用程序。你有代码,它可以编译,并且所有单元测试都在你的持续集成服务器上通过,但是需要几天时间才能将新版本发布到可公开访问的 UAT 环境中。在如此短的时间内期待演示是不是不合理?

Yesterday your boss asked you to demonstrate the great new features of your system to a customer, but you can’t show them anything. All your developers are halfway through developing new features and none of them can run the application right now. You have code, it compiles, and all the unit tests pass on your continuous integration server, but it takes a couple of days to release the new version into the publicly accessible UAT environment. Isn’t it unreasonable to expect the demo at such short notice?

你在生产中有一个严重的错误。它每天都在为您的企业赔钱。您知道解决方法是什么:在您的三层系统的所有三层中使用的库中的单行代码,以及对一个数据库表的相应更改。但是上一次您将软件的新版本发布到生产环境时,花了一个周末的时间工作到凌晨 3 点,不久之后进行部署的人就厌恶地辞职了。您知道下一个版本将在周末超支,这意味着该应用程序将在工作周期间停机一段时间。如果只有企业了解我们的问题。

You have a critical bug in production. It is losing money for your business every day. You know what the fix is: A one-liner in a library that is used in all three layers of your three-tier system, and a corresponding change to one database table. But the last time you released a new version of your software to production it took a weekend of working until 3 A.M., and the person who did the deployment quit in disgust shortly afterward. You know the next release is going to overrun the weekend, which means the application will be down for a period during the business week. If only the business understood our problems.

这些问题虽然太普遍了,但并不是软件开发过程中不可避免的结果:它们是出现问题的迹象。软件发布应该是一个快速、可重复的过程。如今,许多公司在一天内发布多个版本。即使对于具有复杂代码库的大型项目,这也是可能的。在本书中,我们将向您展示这是如何完成的。

These problems, although all too common, are not an inevitable outcome of the software development process: They are an indication that something is wrong. Software release should be a fast, repeatable process. These days, many companies are putting out multiple releases in a day. This is possible even with large projects with complex codebases. In this book, we will show you how this is done.

Mary 和 Tom Poppendieck 问道:“您的组织需要多长时间才能部署仅涉及一行代码的更改?你这样做是在可重复的、可靠的基础上吗?”1从决定需要进行更改到将其投入生产的时间称为周期时间,它是任何项目的重要指标。

Mary and Tom Poppendieck asked, “How long would it take your organization to deploy a change that involves just one single line of code? Do you do this on a repeatable, reliable basis?”1 The time from deciding that you need to make a change to having it in production is known as the cycle time, and it is a vital metric for any project.

在许多组织中,周期时间以周或月为单位,发布过程当然不可重复或不可靠。它是手动的,通常需要一组人才能将软件部署到测试或暂存环境中,更不用说部署到生产环境中了。然而,我们遇到过同样复杂的项目,这些项目最初是这样的,但经过大量的重新设计后,团队能够在几个小时甚至几分钟的周期内完成关键修复。这是可能的,因为创建了一个完全自动化、可重复、可靠的流程,用于在构建、部署、测试和发布流程的各个阶段进行更改。自动化是关键。它允许开发人员、测试人员和操作人员执行软件创建和部署中涉及的所有常见任务,

In many organizations, cycle time is measured in weeks or months, and the release process is certainly not repeatable or reliable. It is manual and often requires a team of people to deploy the software even into a testing or staging environment, let alone into production. However, we have come across equally complex projects which started out like this but where, after extensive reengineering, teams were able to achieve a cycle time of hours or even minutes for a critical fix. This was possible because a fully automated, repeatable, reliable process was created for taking changes through the various stages of the build, deploy, test, and release process. Automation is the key. It allows all of the common tasks involved in the creation and deployment of software to be performed by developers, testers, and operations personnel, at the push of a button.

这本书描述了如何通过缩短从创意到实现商业价值的路径(周期时间)来彻底改变软件交付。

This book describes how to revolutionize software delivery by making the path from idea to realized business value—the cycle time—shorter and safer.

软件在到达用户手中之前不会产生任何收入。这是显而易见的,但在大多数组织中,将软件发布到生产环境是一个手动密集型、容易出错且有风险的过程。虽然以月为单位的周期时间很常见,但许多公司做得比这更糟:超过一年的发布周期并非未知。对于大公司来说,从产生想法到发布实现它的代码之间的每一周延迟都可能意味着数百万美元的机会成本——但这些往往是周期时间最长的。

Software delivers no revenue until it is in the hands of its users. This is obvious, but in most organizations the release of software into production is a manually intensive, error-prone, and risky process. While a cycle time measured in months is common, many companies do much worse than this: Release cycles of more than a year are not unknown. For large companies every week of delay between having an idea and releasing the code that implements it can represent millions of dollars in opportunity costs—and yet these are often the ones with the longest cycle times.

尽管如此,允许低风险软件交付的机制和流程还没有成为当今大多数软件开发项目结构的一部分。

Despite all this, the mechanisms and processes that allow for low-risk delivery of software have not become part of the fabric in most of today’s software development projects.

我们的目标是使软件从开发人员手中交付到生产成为一个可靠、可预测、可见且很大程度上自动化的过程,并具有易于理解、可量化的风险。使用我们在本书中描述的方法,可以在几分钟或几小时内从产生想法到交付将其实施到生产中的工作代码,同时提高所交付软件的质量。

Our aim is to make the delivery of software from the hands of developers into production a reliable, predictable, visible, and largely automated process with well-understood, quantifiable risks. Using the approach that we describe in this book, it is possible to go from having an idea to delivering working code that implements it into production in a matter of minutes or hours, while at the same time improving the quality of the software thus delivered.

与成功交付软件相关的绝大部分成本是在首次发布之后产生的。这是支持、维护、添加新功能和修复缺陷的成本。对于通过迭代过程交付的软件尤其如此,其中第一个版本包含为客户提供价值的最少量功能。因此,本书的标题,持续交付,取自敏捷宣言的第一原则:“我们的首要任务是通过尽早持续交付有价值的软件来满足客户”[bibNp0]。这反映了现实:对于成功的软件,首次发布只是交付过程的开始。

The vast majority of the cost associated with delivering successful software is incurred after the first release. This is the cost of support, maintenance, adding new features, and fixing defects. This is especially true of software delivered via iterative processes, where the first release contains the minimum amount of functionality providing value to the customer. Hence the title of this book, Continuous Delivery, which is taken from the first principle of the Agile Manifesto: “Our highest priority is to satisfy the customer through early and continuous delivery of valuable software” [bibNp0]. This reflects the reality: For successful software, the first release is just the beginning of the delivery process.

我们在本书中描述的所有技术都减少了与向用户交付软件新版本相关的时间和风险。他们通过增加反馈和改进负责交付的开发、测试和运营人员之间的协作来做到这一点。这些技术确保当您需要修改应用程序时,无论是修复错误还是交付新功能,从进行修改到部署和使用结果之间的时间尽可能短,在问题易于修复时及早发现问题,并且充分了解相关风险。

All the techniques we describe in this book reduce the time and risks associated with delivering new versions of your software to users. They do this by increasing feedback and improving collaboration between the development, testing, and operations personnel responsible for delivery. These techniques ensure that when you need to modify applications, either to fix bugs or deliver new features, the time between making modifications and having the results deployed and in use is as low as possible, problems are found early when they are easy to fix, and associated risks are well understood.

这本书是为谁而写的,它涵盖了哪些内容?

Who Is This Book for, and What Does It Cover?

本书的主要目标之一是改善负责交付软件的人员之间的协作。特别是,我们考虑了开发人员、测试人员、系统和数据库管理员以及经理。

One of the major aims of this book is to improve collaboration between the people responsible for delivering software. In particular, we have in mind developers, testers, systems and database administrators, and managers.

我们涵盖的主题从传统的配置管理、源代码控制、发布规划、审计、合规性和集成到构建、测试和部署过程的自动化。我们还描述了自动验收测试、依赖管理、数据库迁移以及测试和生产环境的创建和管理等技术。

We cover topics from traditional configuration management, source code control, release planning, auditing, compliance, and integration to the automation of your building, testing, and deployment processes. We also describe techniques such as automated acceptance testing, dependency management, database migration, and the creation and management of testing and production environments.

许多参与创建软件的人认为这些活动是编写代码的次要活动。然而,根据我们的经验,它们会占用大量时间和精力,并且对于成功的软件交付至关重要。当围绕这些活动的风险没有得到充分管理时,它们最终可能会花费大量资金,通常比最初构建软件的成本还要高。本书提供了了解这些风险所需的信息,更重要的是,它描述了减轻这些风险的策略。

Many people involved in creating software consider these activities secondary to writing code. However, in our experience they take up a great deal of time and effort, and are critical to successful software delivery. When the risks surrounding these activities are not managed adequately, they can end up costing a lot of money, often more than the cost of building the software in the first place. This book provides the information that you need to understand these risks and, more importantly, describes strategies to mitigate them.

这是一个雄心勃勃的目标,当然我们不可能在一本书中详细涵盖所有这些主题。事实上,我们冒着疏远我们每个目标受众的风险:开发人员,因为未能深入处理架构、行为驱动开发和重构等主题;测试人员,没有在探索性测试和测试管理策略上花费足够的时间;运维人员没有适当注意容量规划、数据库迁移和生产监控。

This is an ambitious aim, and of course we can’t cover all these topics in detail in one book. Indeed we run the risk of alienating each of our target audiences: developers, by failing to treat topics such as architecture, behavior-driven development, and refactoring in depth; testers, by not spending sufficient time on exploratory testing and test management strategies; operations personnel, by not paying due attention to capacity planning, database migration, and production monitoring.

但是,存在详细解决这些主题中的每一个主题的书籍。我们认为文献中缺少一本书来讨论所有移动部分如何组合在一起:配置管理、自动化测试、持续集成和部署、数据管理、环境管理和发布管理。精益软件开发运动教导的一件事是优化整体很重要。为了做到这一点,需要一种整体方法,将交付过程的每个部分和参与其中的每个人联系在一起。只有当您可以控制从引入到发布的每项变更的进展时,您才能开始优化和提高软件交付的质量和速度。

However, books exist that address each of these topics in detail. What we think is lacking in the literature is a book that discusses how all the moving parts fit together: configuration management, automated testing, continuous integration and deployment, data management, environment management, and release management. One of the things that the lean software development movement teaches is that it is important to optimize the whole. In order to do this, a holistic approach is necessary that ties together every part of the delivery process and everybody involved in it. Only when you have control over the progression of every change from introduction to release can you begin to optimize and improve the quality and speed of software delivery.

我们的目标是提出一种整体方法,以及该方法中涉及的原则。我们将为您提供决定如何在您自己的项目中应用这些实践所需的信息。我们不相信软件开发的任何方面都存在“一刀切”的方法,更不用说像企业系统的配置管理和操作控制这样大的主题领域了。然而,我们在本书广泛适用于各种不同的软件项目——大的、小的、高技术的或短期的早期价值冲刺。

Our aim is to present a holistic approach, as well as the principles involved in this approach. We will provide you with the information that you will need to decide how to apply these practices in your own projects. We do not believe that there is a “one size fits all” approach to any aspect of software development, let alone a subject area as large as the configuration management and operational control of an enterprise system. However, the fundamentals that we describe in this book are widely applicable to all sorts of different software projects—big, small, highly technical or short sprints to early value.

当您开始将这些原则付诸实践时,您会发现需要针对您的特定情况提供更多细节的领域。本书末尾有参考书目,以及指向其他在线资源的指针,您可以在其中找到有关我们涵盖的每个主题的更多信息。

As you begin to put these principles into practice, you will discover the areas where more detail is required for your particular situation. There is a bibliography at the end of this book, as well as pointers to other resources online where you can find more information on each of the topics that we cover.

本书由三部分组成。第一部分介绍了持续交付背后的原则以及支持它所必需的实践。第二部分描述了本书的中心范式——我们称之为部署管道的模式。第三部分更详细地介绍了支持部署管道的生态系统——支持增量开发的技术;高级版本控制模式;基础设施、环境和数据管理;和治理。

This book consists of three parts. The first part presents the principles behind continuous delivery and the practices necessary to support it. Part two describes the central paradigm of the book—a pattern we call the deployment pipeline. The third part goes into more detail on the ecosystem that supports the deployment pipeline—techniques to enable incremental development; advanced version control patterns; infrastructure, environment and data management; and governance.

其中许多技术可能看起来仅适用于大规模应用程序。虽然我们的大部分经验确实是大型应用程序,但我们相信即使是最小的项目也可以从这些技术的全面基础中受益,原因很简单,因为项目会增长。你在开始一个小项目时所做的决定将对其发展产生不可避免的影响,并且通过以正确的方式开始,你将为自己(或你之后的人)避免更多的痛苦。

Many of these techniques may appear to apply only to large-scale applications. While it is true that much of our experience is with large applications, we believe that even the smallest projects can benefit from a thorough grounding in these techniques, for the simple reason that projects grow. The decisions that you make when starting a small project will have an inevitable impact on its evolution, and by starting off in the right way, you will save yourself (or those who come after you) a great deal of pain further down the line.

您的作者拥有精益和迭代软件开发理念的背景。我们的意思是,我们的目标是快速迭代地向用户交付有价值的、可工作的软件,不断努力消除交付过程中的浪费。我们描述的许多原则和技术最初是在大型敏捷项目的背景下开发的。然而,我们在本书中介绍的技术具有普遍适用性。我们的大部分重点是通过更好的可见性和更快的反馈来改善协作。这将对每个项目产生积极影响,无论它是否使用迭代软件开发过程。

Your authors share a background in lean and iterative software development philosophies. By this we mean that we aim to deliver valuable, working software to users rapidly and iteratively, working continuously to remove waste from the delivery process. Many of the principles and techniques that we describe were first developed in the context of large agile projects. However, the techniques that we present in this book are of general applicability. Much of our focus is on improving collaboration through better visibility and faster feedback. This will have a positive impact on every project, whether or not it uses iterative software development processes.

我们努力确保章节甚至章节可以单独阅读。至少,我们希望您需要了解的任何内容以及对更多信息的引用都清楚地标明并且易于访问,以便您可以将本书作为参考。

We have tried to ensure that chapters and even sections can be read in isolation. At the very least, we hope that anything you need to know, as well as references to further information, are clearly sign-posted and accessible so that you can use this book as a reference.

值得一提的是,我们在处理所涵盖的主题时并不追求学术上的严谨。市场上有更多的理论书籍,其中许多提供了有趣的阅读和见解。特别是,我们不会在标准上花太多时间,而是专注于每个从事软件项目的人都会发现有用的经过实战检验的技能和技术,并清楚、简单地解释它们,以便它们可以在现实世界中每天使用. 在适当的情况下,我们将提供一些战争故事来说明这些技术,以帮助将它们放在上下文中。

We should mention that we don’t aim for academic rigor in our treatment of the subjects covered. There are plenty of more theoretical books on the market, many of which provide interesting reading and insights. In particular, we will not spend much time on standards, concentrating instead on battle-tested skills and techniques every person working on a software project will find useful, and explaining them clearly and simply so that they can be used every day in the real world. Where appropriate, we will provide some war stories illustrating these techniques to help place them in context.

概论

Conspectus

我们认识到并不是每个人都想从头到尾读完这本书。我们编写它的目的是,一旦您了解了介绍,您就可以通过几种不同的方式对其进行攻击。这涉及到一定程度的重复,但如果您决定从头到尾阅读它,希望不会达到令人乏味的程度。

We recognize that not everyone will want to read this book from end to end. We have written it so that once you have covered the introduction, you can attack it in several different ways. This has involved a certain amount of repetition, but hopefully not at a level that becomes tedious if you do decide to read it cover-to-cover.

本书由三部分组成。第一部分,第 1 章到第4章,带您了解定期、可重复、低风险发布的基本原则以及支持这些原则的实践。第二部分,第 5 章到第10章,描述了部署管道。第 11 章开始,我们将深入探讨支持持续交付的生态系统。

This book consists of three parts. The first part, Chapters 1 to 4, takes you through the basic principles of regular, repeatable, low-risk releases and the practices that support them. Part two, Chapters 5 through 10, describe the deployment pipeline. From Chapter 11 we dive into the ecosystem that supports continuous delivery.

我们建议大家阅读第 1 章我们相信,对软件发布过程不熟悉的人,甚至是经验丰富的开发人员,都会发现大量材料挑战他们对进行专业软件开发意味着什么的看法。本书的其余部分可以在您闲暇时或在恐慌时阅读。

We recommend that everybody read Chapter 1. We believe that people who are new to the process of releasing software, even experienced developers, will find plenty of material challenging their view of what it means to do professional software development. The rest of the book can be dipped into either at your leisure—or when in a panic.

第一部分——基础

Part I—Foundations

第一部分描述了解部署管道的先决条件。每一章都建立在最后一章的基础上。

Part I describes the prerequisites for understanding the deployment pipeline. Each chapter builds upon the last.

第 1 章“交付软件的问题”首先描述了我们在许多软件开发团队中看到的一些常见反模式,然后继续描述我们的目标以及如何实现它。最后,我们列出了本书其余部分所依据的软件交付原则。

Chapter 1, “The Problem of Delivering Software,” starts by describing some common antipatterns that we see in many software development teams, and moves on to describe our goal and how to realize it. We conclude by setting out the principles of software delivery upon which the rest of the book is based.

第 2 章“配置管理”阐述了如何管理构建、部署、测试和发布应用程序所需的一切,从源代码和构建脚本到环境和应用程序配置。

Chapter 2, “Configuration Management,” sets out how to manage everything required to build, deploy, test, and release your application, from source code and build scripts to your environment and application configuration.

第 3 章,“持续集成”涵盖了针对您对应用程序所做的每个更改构建和运行自动化测试的实践,以便您可以确保您的软件始终处于工作状态。

Chapter 3, “Continuous Integration,” covers the practice of building and running automated tests against every change you make to your application so you can ensure that your software is always in a working state.

第 4 章,“实施测试策略”,介绍了构成每个项目不可或缺的一部分的各种手动和自动测试,并讨论了如何决定哪种策略适合您的项目。

Chapter 4, “Implementing a Testing Strategy,” introduces the various kinds of manual and automated testing that form an integral part of every project, and discusses how to decide which strategy is appropriate for your project.

第二部分——部署管道

Part II—The Deployment Pipeline

本书的第二部分详细介绍了部署管道,包括如何实现管道中的各个阶段。

The second part of the book covers the deployment pipeline in detail, including how to implement the various stages in the pipeline.

第 5 章,“部署流水线剖析”,讨论了构成本书核心的模式——一个自动处理从签入到发布的所有更改的过程。我们还讨论了如何在团队和组织层面实施管道。

Chapter 5, “Anatomy of the Deployment Pipeline,” discusses the pattern that forms the core of this book—an automated process for taking every change from check-in to release. We also discuss how to implement pipelines at both the team and organizational levels.

第 6 章“构建和部署脚本”讨论了可用于创建自动化构建和部署过程的脚本技术,以及使用它们的最佳实践。

Chapter 6, “Build and Deployment Scripting,” discusses scripting technologies that can be used for creating automated build and deployment processes, and the best practices for using them.

第 7 章“提交阶段”介绍了管道的第一阶段,这是一组自动化流程,应在应用程序中引入任何更改时触发。我们还讨论了如何创建一个快速、有效的提交测试套件。

Chapter 7, “The Commit Stage,” covers the first stage of the pipeline, a set of automated processes that should be triggered the moment any change is introduced into your application. We also discuss how to create a fast, effective commit test suite.

第 8 章“自动化验收测试”介绍了从分析到实施的自动化验收测试。我们讨论了为什么验收测试对于持续交付至关重要,以及如何创建具有成本效益的验收测试套件来保护您的应用程序的宝贵功能。

Chapter 8, “Automated Acceptance Testing,” presents automated acceptance testing, from analysis to implementation. We discuss why acceptance tests are essential to continuous delivery, and how to create a cost-effective acceptance test suite that will protect your application’s valuable functionality.

第 9 章“测试非功能性需求”讨论了非功能性需求,重点是能力测试。我们描述了如何创建容量测试,以及如何设置容量测试环境。

Chapter 9, “Testing Nonfunctional Requirements,” discusses nonfunctional requirements, with an emphasis on capacity testing. We describe how to create capacity tests, and how to set up a capacity testing environment.

第 10 章,“部署和发布应用程序”,涵盖了自动化测试之后发生的事情:将发布候选者按钮提升到手动测试环境、UAT、暂存和最终发布,包括持续部署、回滚和零停机发布。

Chapter 10, “Deploying and Releasing Applications,” covers what happens after automated testing: push-button promotion of release candidates to manual testing environments, UAT, staging, and finally release, taking in essential topics such as continuous deployment, roll backs, and zero-downtime releases.

第三部分——交付生态系统

Part III—The Delivery Ecosystem

本书的最后一部分讨论了支持部署管道的横切实践和技术。

The final part of the book discusses crosscutting practices and techniques that support the deployment pipeline.

第 11 章“管理基础设施和环境”涵盖了环境的自动创建、管理和监控,包括虚拟化和云计算的使用。

Chapter 11, “Managing Infrastructure and Environments,” covers the automated creation, management, and monitoring of environments, including the use of virtualization and cloud computing.

第 12 章“管理数据”展示了如何在应用程序的整个生命周期中创建和迁移测试和生产数据。

Chapter 12, “Managing Data,” shows how to create and migrate testing and production data through the lifecycle of your application.

第 13 章“管理组件和依赖性”首先讨论了如何在没有分支的情况下始终使您的应用程序保持在可发布状态。然后我们将描述如何将您的应用程序组织为组件的集合,以及如何管理构建和测试它们。

Chapter 13, “Managing Components and Dependencies,” starts with a discussion of how to keep your application in a releasable state at all times without branching. We then describe how to organize your application as a collection of components, and how to manage building and testing them.

第 14 章“高级版本控制”概述了最流行的工具,并详细介绍了使用版本控制的各种模式。

Chapter 14, “Advanced Version Control,” gives an overview of the most popular tools, and goes into detail on the various patterns for using version control.

第 15 章,“管理持续交付”,阐述了风险管理和合规性的方法,并提供了配置和发布管理的成熟度模型。在此过程中,我们讨论了持续交付对业务的价值,以及增量交付的迭代项目的生命周期。

Chapter 15, “Managing Continuous Delivery,” sets out approaches to risk management and compliance, and provides a maturity model for configuration and release management. Along the way, we discuss the value of continuous delivery to the business, and the lifecycle of iterative projects that deliver incrementally.

本书中的网页链接

Web Links in This Book

我们没有放入外部网站的完整链接,而是缩短了它们并以这种格式放入密钥:[bibNp0]您可以通过以下两种方式之一转到该链接。要么使用 bit.ly,在这种情况下,示例密钥的 url 将是http://bit.ly/bibNp0或者,您可以使用我们安装在http://continuousdelivery.com/go/的 url 缩短服务,它使用相同的键——所以示例键的 url 是http://continuousdelivery.com/go/bibNp0这个想法是,如果由于某种原因 bit.ly 下线,链接将被保留。如果网页更改地址,我们会尽量使http://continuousdelivery.com/go/上的缩短服务保持最新,所以如果链接在 bit.ly 上不起作用,请尝试这样做。

Rather than putting in complete links to external websites, we have shortened them and put in the key in this format: [bibNp0]. You can go to the link in one of two ways. Either use bit.ly, in which case the url for the example key would be http://bit.ly/bibNp0. Alternatively, you can use a url shortening service we’ve installed at http://continuousdelivery.com/go/ which uses the same keys—so the url for the example key is http://continuousdelivery.com/go/bibNp0. The idea is that if for some reason bit.ly goes under, the links are preserved. If the web pages change address, we’ll try to keep the shortening service at http://continuousdelivery.com/go/ up-to-date, so try that if the links don’t work at bit.ly.

关于封面

About the Cover

Martin Fowler 的签名系列中的所有书籍在封面上都有一座桥。我们最初计划使用铁桥的照片,但它已经被选为该系列的另一本书。因此,我们选择了另一座英国桥梁:福斯铁路桥,这是斯图尔特·哈代 (Stewart Hardy) 在此处拍摄的一张令人惊叹的照片。

All books in Martin Fowler’s Signature Series have a bridge on the cover. We’d originally planned to use a photo of the Iron Bridge, but it had already been chosen for another book in the series. So instead, we chose another British bridge: the Forth Railway Bridge, captured here in a stunning photo by Stewart Hardy.

福斯铁路桥是英国第一座使用钢材建造的桥梁,采用新的西门子-马丁平炉工艺制造,由苏格兰的两家钢厂和威尔士的一家钢厂交付。钢材以制造的管状桁架的形式交付——这是英国桥梁首次使用批量生产的部件。与早期的桥梁不同,设计者约翰·福勒爵士、本杰明·贝克爵士和艾伦·斯图尔特计算了架设应力的发生率、减少未来维护成本的准备,以及风压和温度应力对结构的影响的计算——很多就像我们在软件中提出的功能和非功能需求一样。他们还监督桥梁的建设,以确保满足这些要求。

The Forth Railway Bridge was the first bridge in the UK constructed using steel, manufactured using the new Siemens-Martin open-hearth process, and delivered from two steel works in Scotland and one in Wales. The steel was delivered in the form of manufactured tubular trusses—the first time a bridge in the UK used mass-produced parts. Unlike earlier bridges, the designers, Sir John Fowler, Sir Benjamin Baker, and Allan Stewart, made calculations for incidence of erection stresses, provisions for reducing future maintenance costs, and calculations for wind pressures and the effect of temperature stresses on the structure—much like the functional and nonfunctional requirements we make in software. They also supervised the construction of the bridge to ensure these requirements were met.

这座桥的建设涉及 4,600 多名工人,其中约有 100 人不幸死亡,数百人致残。然而,最终的结果却是工业革命的奇迹之一:在 1890 年竣工时,它是世界上最长的桥梁,到 21 世纪初,它仍然是世界上第二长的悬臂桥。就像一个长期存在的软件项目一样,桥梁需要不断维护。这是作为设计的一部分计划的,桥梁的附属工程不仅包括维修车间和院子,还包括达尔梅尼车站大约五十间房屋的铁路“殖民地”。桥梁的剩余使用寿命估计超过 100 年。

The bridge’s construction involved more than 4,600 workers, of whom tragically around one hundred died and hundreds more were crippled. However, the end result is one of the marvels of the industrial revolution: At the time of completion in 1890 it was the longest bridge in the world, and at the start of the 21st century it remains the world’s second longest cantilever bridge. Like a long-lived software project, the bridge needs constant maintenance. This was planned for as part of the design, with ancillary works for the bridge including not only a maintenance workshop and yard but a railway “colony” of some fifty houses at Dalmeny Station. The remaining working life of the bridge is estimated at over 100 years.

版权所有

Colophon

本书是直接用 DocBook 编写的。Dave 在 TextMate 中编辑文本,Jez 使用 Aquamacs Emacs。这些图表是使用 OmniGraffle 创建的。Dave 和 Jez 通常不在世界的同一地区,并且通过将所有内容签入 Subversion 来进行协作。我们还采用了持续集成,使用运行 dblatex 的 CruiseControl.rb 服务器,每当我们中的一个人提交更改时生成该书的 PDF。

This book was written directly in DocBook. Dave edited the text in TextMate, and Jez used Aquamacs Emacs. The diagrams were created with OmniGraffle. Dave and Jez were usually not in the same part of the world, and collaborated by having everything checked in to Subversion. We also employed continuous integration, using a CruiseControl.rb server that ran dblatex to produce a PDF of the book every time one of us committed a change.

在本书付印前一个月,Dmitry Kirsanov 和 Alina Kirsanova 开始了制作工作,通过他们的 Subversion 存储库、电子邮件和共享的 Google Docs 表格与作者协作以进行协调。Dmitry 负责在 XEmacs 中对 DocBook 源代码进行复制编辑,而 Alina 负责其他所有工作:使用自定义 XSLT 样式表和 XSL-FO 格式化程序排版页面,根据源代码中作者的索引标签编译和编辑索引,最后校对这本书。

A month before the book went to print, Dmitry Kirsanov and Alina Kirsanova started the production work, collaborating with the authors through their Subversion repository, email, and a shared Google Docs table for coordination. Dmitry worked on copyediting of the DocBook source in XEmacs, and Alina did everything else: typesetting the pages using a custom XSLT stylesheet and an XSL-FO formatter, compiling and editing the Index from the author’s indexing tags in the source, and final proofreading of the book.

致谢

Acknowledgments

许多人为本书做出了贡献。我们要特别感谢我们的审稿人:David Clack、Leyna Cotran、Lisa Crispin、Sarah Edrie、Damon Edwards、Martin Fowler、James Kovacs、Bob Maksimchuk、Elliotte Rusty Harold、Rob Sanheim 和 Chris Smith。我们还要特别感谢 Addison-Wesley 的编辑和制作团队:Chris Guzikowski、Raina Chrobak、Susan Zahn、Kristy Hart 和 Andy Beaster。德米特里·基尔萨诺夫 (Dmitry Kirsanov) 和阿丽娜·基尔萨诺娃 (Alina Kirsanova) 出色地完成了本书的文字编辑和校对工作,并使用他们的全自动系统进行了排版。

Many people have contributed to this book. In particular, we’d like to thank our reviewers: David Clack, Leyna Cotran, Lisa Crispin, Sarah Edrie, Damon Edwards, Martin Fowler, James Kovacs, Bob Maksimchuk, Elliotte Rusty Harold, Rob Sanheim, and Chris Smith. We’d also like to extend special thanks to our editorial and production team at Addison-Wesley: Chris Guzikowski, Raina Chrobak, Susan Zahn, Kristy Hart, and Andy Beaster. Dmitry Kirsanov and Alina Kirsanova did a fantastic job of copyediting and proofreading the book, and typesetting it using their fully automated system.

我们的许多同事在本书中的思想发展过程中发挥了重要作用,包括(排名不分先后)Chris Read、Sam Newman、Dan North、Dan Worthington-Bodart、Manish Kumar、Kraig Parkinson、Julian Simpson、Paul Julius、Marco Jansen , 杰弗里·弗雷德里克, 阿杰·戈尔, 克里斯·特纳, 保罗·哈曼特, 胡凯, 乔延东, 乔梁, 德里克·杨, Julias Shaw, Deepthi, Mark Chang, Dante Briones, 李光磊, Erik Doernenburg, Kraig Parkinson, Ram Narayanan, Mark Rickmeier , 克里斯·史蒂文森, 杰·弗劳尔斯, 杰森·桑基, 丹尼尔·奥斯特迈尔, 罗尔夫·拉塞尔, 乔恩·蒂尔森, 蒂莫西·里维斯, 本·怀斯, 蒂姆·哈丁, 蒂姆·布朗, Pavan Kadambi Sudarshan, Stephen Foreshew, Yogi Kulkarni, David Rice, Chad Wathington, Jonny LeRoy,和克里斯 Briesemeister。

Many of our colleagues have been instrumental in developing the ideas in this book, including (in no particular order) Chris Read, Sam Newman, Dan North, Dan Worthington-Bodart, Manish Kumar, Kraig Parkinson, Julian Simpson, Paul Julius, Marco Jansen, Jeffrey Fredrick, Ajey Gore, Chris Turner, Paul Hammant, Hu Kai, Qiao Yandong, Qiao Liang, Derek Yang, Julias Shaw, Deepthi, Mark Chang, Dante Briones, Li Guanglei, Erik Doernenburg, Kraig Parkinson, Ram Narayanan, Mark Rickmeier, Chris Stevenson, Jay Flowers, Jason Sankey, Daniel Ostermeier, Rolf Russell, Jon Tirsen, Timothy Reaves, Ben Wyeth, Tim Harding, Tim Brown, Pavan Kadambi Sudarshan, Stephen Foreshew, Yogi Kulkarni, David Rice, Chad Wathington, Jonny LeRoy, and Chris Briesemeister.

Jez 要感谢他的妻子 Rani,感谢她成为他所希望的最亲密的伴侣,并在他写这本书时脾气暴躁时为他打气。他还感谢女儿阿姆丽塔 (Amrita) 的喋喋不休、拥抱和灿烂的笑容。他还非常感谢他在 ThoughtWorks 的同事们,感谢他们使 ThoughtWorks 成为如此鼓舞人心的工作场所,并感谢辛迪·米切尔 (Cyndi Mitchell) 和马丁·福勒 (Martin Fowler) 对本书的支持。最后,非常感谢创建 CITCON 的 Jeffrey Fredrick 和 Paul Julius,以及他在那里遇到的进行了许多精彩对话的人们。

Jez would like to thank his wife, Rani, for being the most loving partner he could wish for, and for cheering him up when he was grumpy during the writing of this book. He also thanks his daughter, Amrita, for her babbling, cuddles, and big gummy smiles. He is also profoundly grateful to his colleagues at ThoughtWorks for making it such an inspiring place to work, and to Cyndi Mitchell and Martin Fowler for their support of this book. Finally, a big shout out to Jeffrey Fredrick and Paul Julius for creating CITCON, and to the people he met there for many great conversations.

戴夫要感谢他的妻子凯特和孩子汤姆和本,感谢他们在这个项目和许多其他项目的每一点上的不懈支持。他还想特别提到 ThoughtWorks,虽然它不再是他的雇主,但它提供了启发和鼓励的环境在那里工作的人们,从而培养了一种寻找解决方案的创造性方法,其中许多内容出现在本书的页面中。此外,他还要感谢他的现任雇主 LMAX,特别要感谢 Martin Thompson,感谢他们在世界一流的高新技术企业极具挑战性的技术环境中支持、信任并愿意采用本书中描述的技术。性能计算。

Dave would like to thank his wife Kate, and children Tom and Ben, for their unfailing support at every point, in this project and in many others. He would also like to make a special mention of ThoughtWorks, who, although no longer his employer, provided an environment of enlightenment and encouragement for the people that worked there, thus fostering a creative approach to finding solutions, many of which populate the pages of this book. In addition, he would like to thank his current employer, LMAX, with a special mention for Martin Thompson, for their support, trust, and willing adoption of the techniques described in this book in an intensely challenging technical environment of world-class high-performance computing.

关于作者

About the Authors

杰斯谦虚自从 11 岁获得他的第一个 ZX Spectrum 以来,他就对计算机和电子产品着迷,并花了几年时间在 6502 和 ARM 汇编器和 BASIC 的 Acorn 机器上进行黑客攻击,直到他足够大可以找到一份合适的工作。他于 2000 年进入 IT 行业,正好赶上互联网泡沫破灭。从那时起,他担任过开发人员、系统管理员、培训师、顾问、经理和演讲者。他曾使用各种平台和技术,为非营利组织、电信、金融服务和在线零售公司提供咨询服务。自 2004 年以来,他在北京、班加罗尔、伦敦和旧金山的 ThoughtWorks 和 ThoughtWorks Studios 工作。他拥有牛津大学物理学和哲学学士学位以及伦敦大学东方和非洲研究学院民族音乐学硕士学位。

Jez Humble has been fascinated by computers and electronics since getting his first ZX Spectrum at age 11, and spent several years hacking on Acorn machines in 6502 and ARM assembler and BASIC until he was old enough to get a proper job. He got into IT in 2000, just in time for the dot-com bust. Since then he has worked as a developer, system administrator, trainer, consultant, manager, and speaker. He has worked with a variety of platforms and technologies, consulting for nonprofits, telecoms, financial services, and online retail companies. Since 2004 he has worked for ThoughtWorks and ThoughtWorks Studios in Beijing, Bangalore, London, and San Francisco. He holds a BA in Physics and Philosophy from Oxford University and an MMus in Ethnomusicology from the School of Oriental and African Studies, University of London. He is presently living in San Francisco with his wife and daughter.

戴夫·法利近 30 年来一直在玩电脑。在那段时间里,他从事过大多数类型的软件——从固件到修补操作系统和设备驱动程序,再到编写各种形状和大小的游戏和商业应用程序。大约 20 年前,他开始在大型分布式系统中工作,研究松耦合、基于消息的系统的开发——SOA 的先驱。他在领导英国和美国大大小小的团队开发复杂软件方面拥有广泛的经验。Dave 是敏捷开发技术的早期采用者,从 1990 年代初开始对商业项目采用迭代开发、持续集成和显着水平的自动化测试。在 ThoughtWorks 的四年半时间里,他磨练了自己的敏捷开发方法,在 ThoughtWorks 担任技术负责人,负责他们一些最大、最具挑战性的项目。Dave 目前在伦敦多元资产交易所 (LMAX) 工作,该组织正在建设世界上性能最高的金融交易所之一,他们依赖本书中描述的所有主要技术。

Dave Farley has been having fun with computers for nearly 30 years. Over that period he has worked on most types of software—from firmware, through tinkering with operating systems and device drivers, to writing games and commercial applications of all shapes and sizes. He started working in large-scale distributed systems about twenty years ago, doing research into the development of loose-coupled, message-based systems—a forerunner of SOA. He has a wide range of experience leading the development of complex software in teams, both large and small, in the UK and USA. Dave was an early adopter of agile development techniques, employing iterative development, continuous integration, and significant levels of automated testing on commercial projects from the early 1990s. He honed his approach to agile development during his four-and-a-half-year stint at ThoughtWorks where he was a technical principal working on some of their biggest and most challenging projects. Dave is currently working for the London Multi-Asset Exchange (LMAX), an organization that is building one of the highest-performance financial exchanges in the world, where they rely upon all of the major techniques described in this book.

第一部分:基础

Part I: Foundations

第 1 章软件交付问题

Chapter 1. The Problem of Delivering Software

介绍

Introduction

作为软件专业人士,我们面临的最重要的问题是:如果有人想到了一个好主意,我们如何尽快将它交付给用户?这本书展示了如何解决这个问题。

The most important problem that we face as software professionals is this: If somebody thinks of a good idea, how do we deliver it to users as quickly as possible? This book shows how to solve this problem.

我们专注于构建、部署、测试和发布过程,关于这方面的文章相对较少。这不是因为我们认为软件开发方法不重要;而是因为我们认为软件开发方法不重要。相反,如果不关注软件生命周期的其他方面——这些方面通常被视为整个问题的外围——就不可能实现可靠、快速、低风险的软件发布,从而获得我们的劳动成果以高效的方式交到我们的用户手中。

We focus on the build, deploy, test, and release process, about which relatively little has been written. This is not because we think that software development approaches are not important; rather, that without a focus on the other aspects of the software lifecycle—aspects that are all too commonly treated as peripheral to the overall problem—it is impossible to achieve reliable, rapid, low-risk software releases that get the fruits of our labors into the hands of our users in an efficient manner.

有许多软件开发方法,但它们主要关注需求管理及其对开发工作的影响。有许多优秀的书籍详细介绍了软件设计、开发和测试的不同方法;但这些也只涵盖了价值流的一小部分,这些价值流为赞助我们努力的人员和组织提供了价值。

There are many software development methodologies, but they focus primarily on requirement management and its impact on the development effort. There are many excellent books that cover in detail different approaches to software design, development, and testing; but these, too, cover only a fragment of the value stream that delivers value to the people and organizations that sponsor our efforts.

一旦确定了需求,设计、开发和测试了解决方案,会发生什么?如何将这些活动结合在一起并协调以使流程尽可能高效和可靠?我们如何使开发人员、测试人员、构建人员和操作人员能够有效地协同工作?

What happens once requirements are identified, solutions designed, developed, and tested? How are these activities joined together and coordinated to make the process as efficient and reliable as we can make it? How do we enable developers, testers, build and operations personnel to work together effectively?

本书描述了一种使软件从开发到发布的有效模式。我们描述了有助于实现此模式的技术和最佳实践,并展示了此方法如何与软件交付的其他方面交互。

This book describes an effective pattern for getting software from development to release. We describe techniques and best practices that help to implement this pattern and show how this approach interfaces with other aspects of software delivery.

图 1.1 部署流水线

Figure 1.1 The deployment pipeline

图片

本书的核心模式是部署管道部署管道本质上是应用程序构建、部署、测试和发布过程的自动化实现。每个组织在部署管道的实施方面都会有所不同,具体取决于它们的价值用于发布软件的流,但管理它们的原则没有变化。图 1.1给出了部署管道的示例

The pattern that is central to this book is the deployment pipeline. A deployment pipeline is, in essence, an automated implementation of your application’s build, deploy, test, and release process. Every organization will have differences in the implementation of their deployment pipelines, depending on their value stream for releasing software, but the principles that govern them do not vary. An example of a deployment pipeline is given in Figure 1.1.

部署管道的工作方式,在一段话中,如下所示。对应用程序的配置、源代码、环境或数据所做的每项更改都会触发管道的新实例的创建。管道中的第一步是创建二进制文件和安装程序。管道的其余部分对二进制文件运行一系列测试,以证明它们可以发布。候选发布版通过的每项测试都让我们更有信心,相信这种二进制代码、配置信息、环境和数据的特定组合能够正常工作。如果发布候选通过所有测试,则可以发布。

The way the deployment pipeline works, in a paragraph, is as follows. Every change that is made to an application’s configuration, source code, environment, or data, triggers the creation of a new instance of the pipeline. One of the first steps in the pipeline is to create binaries and installers. The rest of the pipeline runs a series of tests on the binaries to prove that they can be released. Each test that the release candidate passes gives us more confidence that this particular combination of binary code, configuration information, environment, and data will work. If the release candidate passes all the tests, it can be released.

部署流水线在持续集成过程中有其基础,本质上是持续集成的原则得出的逻辑结论。

The deployment pipeline has its foundations in the process of continuous integration and is in essence the principle of continuous integration taken to its logical conclusion.

部署管道的目的有三个。首先,它使构建、部署、测试和发布软件过程的每个部分都对所有相关人员可见,从而有助于协作。其次,它改进了反馈,以便尽早发现并解决问题。最后,它使团队能够通过完全自动化的过程随意将其软件的任何版本部署和发布到任何环境。

The aim of the deployment pipeline is threefold. First, it makes every part of the process of building, deploying, testing, and releasing software visible to everybody involved, aiding collaboration. Second, it improves feedback so that problems are identified, and so resolved, as early in the process as possible. Finally, it enables teams to deploy and release any version of their software to any environment at will through a fully automated process.

一些常见的发布反模式

Some Common Release Antipatterns

软件发布的那一天往往是紧张的一天。为什么会这样?对于大多数项目,正是与流程相关的风险程度使发布成为一个可怕的时刻。

The day of a software release tends to be a tense one. Why should this be the case? For most projects, it is the degree of risk associated with the process that makes release a scary time.

在许多软件项目中,发布是一个手动密集型过程。托管软件的环境通常由运营或 IS 团队单独打造。安装了应用程序依赖的第三方软件。应用程序本身的软件工件被复制到生产主机环境中。配置信息是通过 Web 服务器、应用程序服务器或系统的其他第三方组件的管理控制台复制或创建的。复制参考数据,最后启动应用程序,如果是分布式或面向服务的应用程序,则逐个启动。

In many software projects, release is a manually intensive process. The environments that host the software are often crafted individually, usually by an operations or IS team. Third-party software that the application relies on is installed. The software artifacts of the application itself are copied to the production host environments. Configuration information is copied or created through the admin consoles of web servers, applications servers, or other third-party components of the system. Reference data is copied, and finally the application is started, piece by piece if it is a distributed or service-oriented application.

紧张的原因应该很清楚:这个过程中有很多错误。如果任何一步没有完美执行,应用程序将无法运行适当地。此时可能根本不清楚错误在哪里,或者哪一步出错了。

The reason for the nervousness should be clear: There is quite a lot to go wrong in this process. If any step is not perfectly executed, the application won’t run properly. At this point it may not be at all clear where the error is, or which step went wrong.

本书的其余部分将讨论如何避免这些风险——如何减轻发布日的压力,以及如何确保每次发布都可预测地可靠。

The rest of this book discusses how to avoid these risks—how to reduce the stress on release days, and how to ensure that each release is predictably reliable.

在此之前,让我们明确一下我们试图避免的流程故障类型。这里有一些常见的反模式,它们会阻止可靠的发布过程,但仍然非常普遍,以至于成为我们行业的规范。

Before that, let’s be clear about the kinds of process failures that we are trying to avoid. Here are a few common antipatterns that prevent a reliable release process, but nevertheless are so common as to be the norm in our industry.

反模式:手动部署软件

Antipattern: Deploying Software Manually

任何规模的大多数现代应用程序部署起来都很复杂,涉及许多活动部件。许多组织手动发布软件。我们的意思是,部署此类应用程序所需的步骤被视为独立且原子的,每个步骤都由个人或团队执行。必须在这些步骤中进行判断,容易出现人为错误。即使情况并非如此,这些步骤的顺序和时间安排的差异也会导致不同的结果。这些差异很少是好的。

Most modern applications of any size are complex to deploy, involving many moving parts. Many organizations release software manually. By this we mean that the steps required to deploy such an application are treated as separate and atomic, each performed by an individual or team. Judgments must be made within these steps, leaving them prone to human error. Even if this is not the case, differences in the ordering and timing of these steps can lead to different outcomes. These differences are rarely good.

这种反模式的迹象是:

The signs of this antipattern are:

• 制作广泛、详细的文档,描述要采取的步骤以及步骤可能出错的方式

• The production of extensive, detailed documentation that describes the steps to be taken and the ways in which the steps may go wrong

• 依赖手动测试来确认应用程序是否正常运行

• Reliance on manual testing to confirm that the application is running correctly

• 经常打电话给开发团队,解释部署在发布日出错的原因

• Frequent calls to the development team to explain why a deployment is going wrong on a release day

• 在发布过程中经常更正发布过程

• Frequent corrections to the release process during the course of a release

• 集群中配置不同的环境,例如具有不同连接池设置的应用程序服务器、具有不同布局的文件系统等。

• Environments in a cluster that differ in their configuration, for example application servers with different connection pool settings, filesystems with different layouts, etc.

• 执行时间超过几分钟的发布

• Releases that take more than a few minutes to perform

• 结果不可预测的发布,通常必须回滚或遇到不可预见的问题

• Releases that are unpredictable in their outcome, that often have to be rolled back or run into unforeseen problems

• 发布日次日凌晨 2 点,睡眼惺忪地坐在显示器前,想办法让它正常工作

• Sitting bleary-eyed in front of a monitor at 2 A.M. the day after the release day, trying to figure out how to make it work

反而...

Instead...

随着时间的推移,部署应该趋向于完全自动化。人类应该执行两项任务来将软件部署到开发中,测试或生产环境:选择版本和环境并按下“部署”按钮。发布打包软件应该涉及一个创建安装程序的自动化过程。

Over time, deployments should tend towards being fully automated. There should be two tasks for a human being to perform to deploy software into a development, test, or production environment: to pick the version and environment and to press the “deploy” button. Releasing packaged software should involve a single automated process that creates the installer.

我们在本书的课程中多次讨论自动化,而且我们知道有些人并不完全相信这个想法。让我们解释一下为什么我们将自动化部署视为不可或缺的目标。

We discuss automation a lot in the course of this book, and we know that some people aren’t totally sold on the idea. Let us explain why we see automated deployment as an indispensable goal.

• 当部署不是完全自动化时,每次执行时都会出现错误。唯一的问题是错误是否重要。即使进行了出色的部署测试,也很难追踪到错误。

• When deployments aren’t fully automated, errors will occur every time they are performed. The only question is whether or not the errors are significant. Even with excellent deployment tests, bugs can be hard to track down.

• 当部署过程不是自动化时,它是不可重复的或不可靠的,导致时间浪费在调试部署错误上。

• When the deployment process is not automated, it is not repeatable or reliable, leading to time wasted on debugging deployment errors.

• 必须记录手动部署过程。维护文档是一项复杂且耗时的任务,涉及多人协作,因此文档通常在任何给定时间都不完整或已过时。一套自动化的部署脚本作为文档,它永远是最新的和完整的,否则部署将无法进行。

• A manual deployment process has to be documented. Maintaining the documentation is a complex and time-consuming task involving collaboration between several people, so the documentation is generally incomplete or out-of-date at any given time. A set of automated deployment scripts serves as documentation, and it will always be up-to-date and complete, or the deployment will not work.

• 自动化部署鼓励协作,因为脚本中的一切都是明确的。文档必须对读者的知识水平做出假设,实际上通常是作为执行部署人员的备忘录编写的,使其对其他人不透明。

• Automated deployments encourage collaboration, because everything is explicit in a script. Documentation has to make assumptions about the level of knowledge of the reader and in reality is usually written as an aide-memoire for the person performing the deployment, making it opaque to others.

• 以上推论:手动部署取决于部署专家。如果他或她正在休假或辞职,你就有麻烦了。

• A corollary of the above: Manual deployments depend on the deployment expert. If he or she is on vacation or quits work, you are in trouble.

• 执行手动部署是乏味和重复的,但需要大量的专业知识。要求专家做枯燥、重复但技术要求高的任务是我们能想到的确保人为错误的最可靠方法,除了睡眠剥夺或醉酒之外。自动化部署可以让您的昂贵、高技能、过度劳累的员工腾出时间来从事更高价值的活动。

• Performing manual deployments is boring and repetitive and yet needs significant degree of expertise. Asking experts to do boring and repetitive, and yet technically demanding tasks is the most certain way of ensuring human error that we can think of, short of sleep deprivation, or inebriation. Automating deployments frees your expensive, highly skilled, overworked staff to work on higher-value activities.

• 测试手动部署过程的唯一方法就是亲自动手。这通常既费时又昂贵。自动化部署过程成本低廉且易于测试。

• The only way to test a manual deployment process is to do it. This is often time-consuming and expensive. An automated deployment process is cheap and easy to test.

• 我们听说人工流程比自动化流程更易于审核。我们对这种说法感到非常困惑。如果采用手动流程,则无法保证已遵循文档。只有自动化过程是完全可审计的。有什么比有效的部署脚本更易于审计的呢?

• We have heard it said that a manual process is more auditable than an automated one. We are completely baffled by this statement. With a manual process, there is no guarantee that the documentation has been followed. Only an automated process is fully auditable. What is more auditable than a working deployment script?

每个人都必须使用自动部署过程,并且它应该是部署软件的唯一方式。此规则可确保部署脚本在需要时运行。我们在本书中描述的原则之一是使用相同的脚本部署到每个环境。如果您使用相同的脚本部署到每个环境,那么在发布日需要之前,部署到生产路径将经过数百甚至数千次测试。如果在发布时出现任何问题,您可以确定它们是特定于环境的配置问题,而不是您的脚本问题。

The automated deployment process must be used by everybody, and it should be the only way in which the software is ever deployed. This discipline ensures that the deployment script will work when it is needed. One of the principles that we describe in this book is to use the same script to deploy to every environment. If you use the same script to deploy to every environment, then the deployment-to-production path will have been tested hundreds or even thousands of times before it is needed on release day. If any problems occur upon release, you can be certain they are problems with environment-specific configuration, not your scripts.

我们确信,手动密集型发布偶尔会顺利进行。我们很可能运气不好,看到的大多是坏的。但是,如果这不被认为是软件生产过程中一个潜在的容易出错的步骤,为什么要举行这样的仪式呢?为什么所有的过程和文档?为什么在周末引入团队?为什么要有人待命以防万一事情进展不顺利?

We are certain that, occasionally, manually intensive releases work smoothly. We may well have been unlucky in having mostly seen the bad ones. However, if this is not recognized as a potentially error-prone step in the process of software production, why is it attended by such ceremony? Why all the process and documentation? Why are the teams of people brought in during weekends? Why have people waiting on standby in case things go less than well?

反模式:仅在开发完成后部署到类生产环境

Antipattern: Deploying to a Production-like Environment Only after Development Is Complete

在这种模式中,软件第一次部署到类似生产的环境(例如,暂存)是在大部分开发工作完成后——至少,按照开发团队的定义“完成”。

In this pattern, the first time the software is deployed to a production-like environment (for example, staging) is once most of the development work is done—at least, “done” as defined by the development team.

图案看起来有点像这样。

The pattern looks a bit like this.

• 如果到目前为止测试人员已经参与了该过程,那么他们已经在开发机器上测试了系统。

• If testers have been involved in the process up to this point, they have tested the system on development machines.

• 发布到暂存区是操作人员第一次与新版本交互。在某些组织中,使用单独的运营团队将软件部署到暂存和生产中。在这种情况下,操作人员第一次看到该软件是在它发布到生产环境的那一天。

• Releasing into staging is the first time that operations people interact with the new release. In some organizations, separate operations teams are used to deploy the software into staging and production. In this case, the first time an operations person sees the software is the day it is released into production.

• 要么类生产环境的成本太高以至于对它的访问受到严格控制,要么没有按时到位,要么没有人费心去创建一个。

• Either a production-like environment is expensive enough that access to it is strictly controlled, or it is not in place on time, or nobody bothered to create one.

• 开发团队组装正确的安装程序、配置文件、数据库迁移和部署文档,以传递给执行实际部署的人员——所有这些都没有在看起来像生产或暂存的环境中进行测试。

• The development team assembles the correct installers, configuration files, database migrations, and deployment documentation to pass to the people who perform the actual deployment—all of it untested in an environment that looks like production or staging.

• 开发团队与实际执行部署以创建此抵押品的人员之间几乎没有协作(如果有的话)。

• There is little, if any, collaboration between the development team and the people who actually perform deployments to create this collateral.

当部署到登台时,将组建一个团队来执行它。有时这个团队拥有所有必要的技能,但通常在非常大的组织中,部署的责任被分配给几个小组。DBA、中间件团队、Web 团队和其他人都参与部署最新版本的应用程序。由于各个步骤从未在staging中测试过,所以它们经常会出错。该文档遗漏了重要步骤。文档和脚本对目标环境的版本或配置做出了错误的假设,导致部署失败。部署团队必须猜测开发团队的意图。

When the deployment to staging occurs, a team is assembled to perform it. Sometimes this team has all the necessary skills, but often in very large organizations the responsibilities for deployment are divided between several groups. DBAs, middleware teams, web teams, and others all take a hand in deploying the latest version of the application. Since the various steps have never been tested in staging, they often have errors. The documentation misses important steps. The documentation and scripts make assumptions about the version or configuration of the target environment that are wrong, causing the deployment to fail. The deployment team has to guess at the intentions of the development team.

通常,由于临时电话、电子邮件和快速修复而导致在部署到暂存过程中出现如此多问题的糟糕协作得到了支持。一个纪律严明的团队会将所有这些沟通纳入部署计划——但这个过程很少见有效。随着压力的增加,为了在分配给部署团队的时间内完成部署,开发和部署团队之间定义的协作流程被颠覆。

Often the poor collaboration that causes so many problems in deployment to staging is shored up with ad-hoc telephone calls, emails, and quick fixes. A very disciplined team will incorporate all of this communication into the deployment plan—but it is rare for this process to be effective. As pressure increases, the defined process for collaboration between the development and deployment teams is subverted, in order to get the deployment done within the time allocated to the deployment team.

在执行部署的过程中,经常会发现关于生产环境的错误假设已经融入到系统设计中。例如,我们参与部署的一个应用程序使用文件系统来缓存数据。这在开发人员工作站上运行良好,但在集群环境中运行不佳。解决这样的问题可能需要很长时间,并且在解决之前不能说应用程序已经部署。

In the process of performing the deployment, it is not uncommon to find that incorrect assumptions about the production environment have been baked into the design of the system. For example, one application we had a hand in deploying used the filesystem to cache data. This worked fine on a developer workstation, but less well in a clustered environment. Solving problems like this one can take a long time, and the application cannot be said to have been deployed until they are resolved.

将应用程序部署到暂存区后,通常会发现新的错误。不幸的是,通常没有时间修复所有问题,因为截止日期快到了,而且在项目的这个阶段,推迟发布日期是不可接受的。因此,最严重的错误会被迅速修补,并且项目经理会存储一份已知缺陷列表以供妥善保管,以便在下一个版本的工作开始时取消优先级。

Once the application is deployed into staging, it is common for new bugs to be found. Unfortunately, there is often no time to fix them all because the deadline is fast approaching and, at this stage of the project, deferring the release date is unacceptable. So the most critical bugs are hurriedly patched up, and a list of known defects is stored by the project manager for safekeeping, to be deprioritized when work begins on the next release.

有时情况可能比这更糟。以下是一些可能会加剧与发布相关的问题的事情。

Sometimes it can be even worse than this. Here are a few things that can exacerbate the problems associated with a release.

• 在处理新应用程序时,第一次部署到暂存可能是最麻烦的。

• When working on a new application, the first deployment to staging is likely to be the most troublesome.

• 发布周期越长,开发团队在部署之前做出错误假设的时间就越长,修复这些假设所需的时间也就越长。

• The longer the release cycle, the longer the development team has to make incorrect assumptions before the deployment occurs, and the longer it will take to fix them.

• 在大型组织中,交付过程在开发、DBA、运营、测试等不同团队之间进行分配,这些孤岛之间的协调成本可能是巨大的,有时会在票务地狱中拖延发布过程。在这个场景中,开发人员、测试人员和运维人员不断地提票(或发送邮件)到彼此执行任何给定的部署——更糟糕的是,解决部署期间出现的问题。

• In large organizations where the delivery process is divided between different groups such as development, DBA, operations, testing, etc., the cost of coordination between these silos can be enormous, sometimes stalling the release process in ticketing hell. In this scenario, developers, testers, and operations personnel are constantly raising tickets (or sending emails) to each other to perform any given deployment—and worse, to resolve problems that arise during deployment.

• 开发环境和生产环境之间的差异越大,在开发过程中必须做出的假设就越不现实。这可能难以量化,但可以肯定的是,如果您在 Windows 机器上开发并部署到 Solaris 集群,您会遇到一些意外。

• The bigger the difference between development and production environments, the less realistic are the assumptions that have to be made during development. This can be difficult to quantify, but it’s a good bet that if you’re developing on a Windows machine and deploying to a Solaris cluster, you are in for some surprises.

• 如果您的应用程序由用户安装或包含用户安装的组件,您可能无法控制他们的环境,尤其是在公司环境之外。在这种情况下,将需要进行大量的额外测试。

• If your application is installed by users or contains components that are, you may not have much control over their environments, especially outside of a corporate setting. In this case, a great deal of extra testing will be required.

反而...

Instead...

补救措施是将测试、部署和发布活动集成到开发过程中。让它们成为开发的正常和持续的一部分,这样当您准备好将系统发布到生产环境时,几乎没有风险,因为您已经在越来越像生产环境的测试环境序列中在许多不同的场合进行了排练. 确保参与软件交付过程的每个人,从构建和发布团队到测试人员再到开发人员,从项目一开始就一起工作。

The remedy is to integrate the testing, deployment, and release activities into the development process. Make them a normal and ongoing part of development so that by the time you are ready to release your system into production there is little to no risk, because you have rehearsed it on many different occasions in a progressively more production-like sequence of test environments. Make sure everybody involved in the software delivery process, from the build and release team to testers to developers, work together from the start of the project.

我们是测试成瘾者,持续集成和持续部署的广泛使用,作为测试我们的软件和部署过程的一种手段,是我们描述的方法的基石。

We are test addicts, and the extensive use of continuous integration and continuous deployment, as a means of testing both our software and our deployment process, is a cornerstone of the approach that we describe.

反模式:生产环境的手动配置管理

Antipattern: Manual Configuration Management of Production Environments

许多组织通过一组操作人员来管理其生产环境的配置。如果需要更改,例如更改数据库连接设置或增加应用程序服务器上线程池中的线程数,则在生产服务器上手动执行。如果保留了此类更改的记录,则它可能是更改管理数据库中的一个条目。

Many organizations manage the configuration of their production environments through a team of operations people. If a change is needed, such as a change to database connection setting or an increase in the number of threads in a thread pool on an application server, then it is carried out manually on the production servers. If a record is kept of such a change, it is probably an entry in a change management database.

这种反模式的迹象是:

Signs of this antipattern are:

• 多次成功部署到暂存区后,部署到生产环境失败。

• Having deployed successfully many times to staging, the deployment into production fails.

• 集群的不同成员表现不同——例如,一个节点比另一个节点承受更少的负载或花费更长的时间来处理请求。

• Different members of a cluster behave differently—for example, one node sustaining less load or taking longer to process requests than another.

• 运营团队需要很长时间来准备发布环境。

• The operations team take a long time to prepare an environment for a release.

• 您无法退回到系统的早期配置,其中可能包括操作系统、应用程序服务器、Web 服务器、RDBMS 或其他基础设施设置。

• You cannot step back to an earlier configuration of your system, which may include operating system, application server, web server, RDBMS, or other infrastructural settings.

• 集群中的服务器无意中具有不同版本的操作系统、第三方基础设施、库或补丁级别。

• Servers in clusters have, unintentionally, different versions of operating systems, third-party infrastructure, libraries, or patch levels.

• 系统配置是通过直接在生产系统上修改配置来执行的。

• Configuration of the system is carried out by modifying the configuration directly on production systems.

反而...

Instead...

每个测试、暂存和生产环境的所有方面,特别是系统的任何第三方元素的配置,都应该通过自动化过程从版本控制中应用。

All aspects of each of your testing, staging, and production environments, specifically the configuration of any third-party elements of your system, should be applied from version control through an automated process.

我们在本书中描述的关键实践之一是配置管理,其中一部分意味着能够重复地重新创建您的应用程序使用的每个基础设施。这意味着操作系统、补丁级别、操作系统配置、您的应用程序堆栈、它的配置、基础设施配置等等都应该被管理。您应该能够准确地重新创建您的生产环境,最好是以自动化的方式。虚拟化可以帮助您开始这方面的工作。

One of the key practices that we describe in this book is configuration management, part of which means being able to repeatably re-create every piece of infrastructure used by your application. That means operating systems, patch levels, OS configuration, your application stack, its configuration, infrastructure configuration, and so forth should all be managed. You should be able to recreate your production environment exactly, preferably in an automated fashion. Virtualization can help you get started with this.

您应该确切地知道生产中的内容。这意味着对生产所做的每项更改都应记录下来并可审计。通常,部署失败是因为有人在上次部署时修补了生产环境,但没有记录更改。事实上,不应该对测试、暂存和生产环境进行手动更改。对这些环境进行更改的唯一方法应该是通过自动化过程。

You should know exactly what is in production. That means that every change made to production should be recorded and auditable. Often, deployments fail because somebody patched the production environment last time they deployed, but the change was not recorded. Indeed it should not be possible to make manual changes to testing, staging, and production environments. The only way to make changes to these environments should be through an automated process.

应用程序通常依赖于其他应用程序。应该可以一目了然地看到每个软件的当前发布版本是什么。

Applications often depend on other applications. It should be possible to see at a glance exactly what the currently released version of every piece of software is.

虽然释放可能令人振奋,但也可能令人筋疲力尽和沮丧。几乎每个版本都涉及最后一刻的更改,例如修复数据库登录详细信息或更新外部服务的 URL。应该有一种引入此类更改的方法,以便对它们进行记录和测试。同样,自动化是必不可少的。更改应该在版本控制中进行,然后通过自动化过程传播到生产环境。

While releases can be exhilarating, they can also be exhausting and depressing. Almost every release involves last-minute changes, such as fixing the database login details or updating the URL for an external service. There should be a way of introducing such changes so that they are both recorded and tested. Again, automation is essential. Changes should be made in version control and then propagated to production through an automated process.

如果部署出错,应该可以使用相同的自动化流程回滚到以前的生产版本。

It should be possible to use the same automated process to roll back to a previous version of production if the deployment goes wrong.

我们能做得更好吗?

Can We Do Better?

你打赌,这本书的目标是描述如何。我们描述的原则、实践和技术旨在使发布变得乏味,即使在复杂的“企业”环境中也是如此。软件发布可以而且应该是一个低风险、频繁、廉价、快速和可预测的过程。这些做法是在过去几年中发展起来的,我们已经看到它们产生了巨大的变化在很多项目中。本书中的所有实践都已经在具有分布式团队的大型企业项目以及小型开发团队中进行了测试。我们知道它们有效,我们知道它们可以扩展到大型项目。

You bet, and the goal of this book is to describe how. The principles, practices, and techniques we describe are aimed at making releases boring, even in complex “enterprise” environments. Software release can—and should—be a low-risk, frequent, cheap, rapid, and predictable process. These practices have been developed over the last few years, and we have seen them make a huge difference in many projects. All of the practices in this book have been tested in large enterprise projects with distributed teams as well as in small development groups. We know that they work, and we know that they scale to large projects.

我们的目标是描述部署管道的使用,结合测试和部署的高度自动化以及全面的配置管理来交付按钮式软件发布。也就是说,按钮式软件发布到任何部署目标——开发、测试或生产。

Our goal is to describe the use of deployment pipelines, combined with high levels of automation of both testing and deployment and comprehensive configuration management to deliver push-button software releases. That is, push-button software releases to any deployment target—development, test, or production.

在此过程中,我们将描述模式本身以及您需要采用的技术来使其发挥作用。我们将就解决您将面临的一些问题的不同方法提供建议。我们发现这种方法的优势远远超过实现它的成本。

Along the way we will describe the pattern itself and the techniques that you will need to adopt to make it work. We will provide advice on different approaches to solving some of the problems that you will face. We have found that the advantages of such an approach vastly outweigh the costs of achieving it.

所有这些都不在任何项目团队的能力范围之外。它不需要严格的流程、重要的文档或很多人。到本章结束时,我们希望您能理解这种方法背后的原则。

None of this is outside the reach of any project team. It does not require rigid process, significant documentation, or lots of people. By the end of this chapter, we hope that you will understand the principles behind this approach.

我们如何实现我们的目标?

How Do We Achieve Our Goal?

正如我们所说,我们作为软件专业人员的目标是尽快为用户提供有用的、可工作的软件。

As we said, our goal as software professionals is to deliver useful, working software to users as quickly as possible.

速度至关重要,因为不交付软件会产生机会成本。只有在您的软件发布后,您才能开始获得投资回报。因此,我们在本书中的两个首要目标之一是找到减少周期时间的方法,即从决定进行更改(无论是错误修复还是功能)到将其提供给用户所需的时间。

Speed is essential because there is an opportunity cost associated with not delivering software. You can only start to get a return on your investment once your software is released. So, one of our two overriding goals in this book is to find ways to reduce cycle time, the time it takes from deciding to make a change, whether a bugfix or a feature, to having it available to users.

快速交付也很重要,因为它可以让您验证您的功能和错误修复是否真的有用。创建应用程序背后的决策者,我们称之为客户,对哪些功能和错误修复将对用户有用做出假设。然而,在通过选择使用该软件进行投票的用户手中之前,它们仍然是假设。因此,将周期时间降至最低至关重要,这样才能建立有效的反馈回路。

Delivering fast is also important because it allows you to verify whether your features and bugfixes really are useful. The decision maker behind the creation of an application, who we’ll call the customer, makes hypotheses about which features and bugfixes will be useful to users. However, until they are in the hands of users who vote by choosing to use the software, they remain hypotheses. It is therefore vital to minimize cycle time so that an effective feedback loop can be established.

有用性的一个重要部分是质量。我们的软件应该适合它的目的。质量不等于完美——正如伏尔泰所说,“完美是优秀的敌人”——但我们的目标应该始终是提供足够质量的软件,为用户带来价值。因此,尽管尽快交付我们的软件很重要,但保持适当的质量水平也很重要。

An important part of usefulness is quality. Our software should be fit for its purpose. Quality does not equal perfection—as Voltaire said, “The perfect is the enemy of the good,”—but our goal should always be to deliver software of sufficient quality to bring value to its users. So while it is important to deliver our software as quickly as possible, it is essential to maintain an appropriate level of quality.

因此,为了稍微完善我们的目标,我们希望找到以高效、快速和可靠的方式交付高质量、有价值的软件的方法。

So, to slightly refine our goal, we want to find ways to deliver high-quality, valuable software in an efficient, fast, and reliable manner.

我们和我们的同行已经发现,为了实现这些目标——低周期时间和高质量——我们需要频繁、自动地发布我们的软件。为什么是这样?

We, and our fellow practitioners, have discovered that in order to achieve these goals—low cycle time and high quality—we need to make frequent, automated releases of our software. Why is this?

自动化。如果构建、部署、测试和发布过程不是自动化的,那么它是不可重复的。每次完成都会有所不同,因为软件、系统配置、环境和发布过程的变化。由于这些步骤是手动的,因此很容易出错,并且无法准确查看所做的事情。这意味着无法控制发布过程,因此无法确保高质量。发布软件往往是一门艺术;它应该是一门工程学科。

Automated. If the build, deploy, test, and release process is not automated, it is not repeatable. Every time it is done, it will be different, because of changes in the software, the configuration of the system, the environments, and the release process. Since the steps are manual, they are error-prone, and there is no way to review exactly what was done. This means there is no way to gain control over the release process, and hence to ensure high quality. Releasing software is too often an art; it should be an engineering discipline.

频繁。如果发布频繁,则发布之间的差异会很小。这显着降低了与发布相关的风险,并使其更容易回滚。频繁发布也会导致更快的反馈——事实上,他们需要它。本书的大部分内容都集中于尽快获得对应用程序及其相关配置(包括其环境、部署过程和数据)的更改的反馈。

Frequent. If releases are frequent, the delta between releases will be small. This significantly reduces the risk associated with releasing and makes it much easier to roll back. Frequent releases also lead to faster feedback—indeed, they require it. Much of this book concentrates on getting feedback on changes to your application and its associated configuration (including its environment, deployment process, and data) as quickly as possible.

反馈对于频繁的自动发布至关重要。反馈有用的三个标准。

Feedback is essential to frequent, automated releases. There are three criteria for feedback to be useful.

• 任何变化,无论是何种形式,都需要触发反馈过程。

• Any change, of whatever kind, needs to trigger the feedback process.

• 必须尽快提供反馈。

• The feedback must be delivered as soon as possible.

• 交付团队必须收到反馈然后采取行动。

• The delivery team must receive feedback and then act on it.

让我们详细研究这三个标准,并考虑如何实现它们。

Let’s examine these three criteria in detail and consider how we can achieve them.

每一个变化都应该触发反馈过程

Every Change Should Trigger the Feedback Process

一个工作的软件应用程序可以有效地分解为四个组件:可执行代码、配置、主机环境和数据。如果它们中的任何一个发生变化,都会导致应用程序行为发生变化。因此,我们需要控制所有这四个组件,并确保对其中任何一个的更改进行验证。

A working software application can be usefully decomposed into four components: executable code, configuration, host environment, and data. If any of them changes, it can lead to a change in the behavior of the application. Therefore we need to keep all four of these components under control and ensure that a change in any one of them is verified.

当对源代码进行更改时,可执行代码也会更改。每次对源代码进行更改时,都必须构建和测试生成的二进制文件。为了控制这个过程,构建和测试二进制文件应该是自动化的。在每次签入时构建和测试应用程序的做法称为持续集成;我们将在第 3 章详细介绍。

Executable code changes when a change is made to the source code. Every time a change is made to the source code, the resulting binary must be built and tested. In order to gain control over this process, building and testing the binary should be automated. The practice of building and testing your application on every check-in is known as continuous integration; we describe it in detail in Chapter 3.

此可执行代码应该是部署到每个环境中的相同可执行代码,无论是测试环境还是生产环境。如果您的系统使用编译语言,您应该确保构建过程的二进制输出(可执行代码)在需要的地方重复使用,并且永远不会重新构建。

This executable code should be the same executable code that is deployed into every environment, whether it is a testing environment or a production environment. If your system uses a compiled language, you should ensure that the binary output of your build process—the executable code—is reused everywhere it is needed and never rebuilt.

环境之间发生的任何变化都应作为配置信息捕获。应测试应用程序配置在任何环境中的任何更改。如果软件要由用户安装,则可能的配置选项应该在具有代表性的示例系统范围内进行测试。配置管理在第 2 章中讨论。

Anything that changes between environments should be captured as configuration information. Any change to an application’s configuration, in whichever environment, should be tested. If the software is to be installed by the users, the possible configuration options should be tested across a representative range of example systems. Configuration management is discussed in Chapter 2.

如果要将应用程序部署到的环境发生变化,则应根据环境的变化对整个系统进行测试。这包括操作系统配置、支持应用程序的软件堆栈、网络配置以及任何基础架构和外部系统的更改。第 11 章涉及管理基础架构和环境,包括测试和生产环境的创建和维护的自动化。

If the environments the application is to be deployed into change, the whole system should be tested with the changes to the environment. This includes changes in the operating system configuration, the software stack that supports the application, the network configuration, and any infrastructure and external systems. Chapter 11 deals with managing infrastructure and environments, including automation of the creation and maintenance of testing and production environments.

最后,如果数据的结构发生变化,也必须测试这种变化。我们将在第 12 章讨论数据管理

Finally, if the structure of the data changes, this change must also be tested. We discuss data management in Chapter 12.

什么是反馈流程?它涉及尽可能以全自动方式测试每个更改。测试会因系统而异,但通常至少包括以下检查。

What is the feedback process? It involves testing every change in a fully automated fashion, as far as possible. The tests will vary depending on the system, but they will usually include at least the following checks.

• 创建可执行代码的过程必须有效。这将验证源代码的语法是否有效。

• The process of creating the executable code must work. This verifies that the syntax of your source code is valid.

• 软件的单元测试必须通过。这会检查您的应用程序代码是否按预期运行。

• The software’s unit tests must pass. This checks that your application’s code behaves as expected.

• 软件应满足某些质量标准,例如测试覆盖率和其他特定于技术的指标。

• The software should fulfill certain quality criteria such as test coverage and other technology-specific metrics.

• 软件的功能验收测试必须通过。这会检查您的应用程序是否符合其业务验收标准——它是否提供了预期的业务价值。

• The software’s functional acceptance tests must pass. This checks that your application conforms to its business acceptance criteria—that it delivers the business value that was intended.

• 软件的非功能测试必须通过。这会检查应用程序在容量、可用性、安全性等方面的表现是否足够好,以满足其用户的需求。

• The software’s nonfunctional tests must pass. This checks that the application performs sufficiently well in terms of capacity, availability, security, and so on to meet its users’ needs.

• 软件必须经过探索性测试并向客户和选定的用户进行演示。这通常是在手动测试环境中完成的。在这部分过程中,产品所有者可能会确定缺少功能,或者我们可能会发现需要修复的错误和需要创建的自动化测试以防止回归。

• The software must go through exploratory testing and a demonstration to the customer and a selection of users. This is typically done from a manual testing environment. In this part of the process, the product owner might decide that there are missing features, or we might find bugs that require fixing and automated tests that need creating to prevent regressions.

这些测试运行的环境必须尽可能与生产环境相似,以验证对我们环境的任何更改都不会影响应用程序的工作能力。

The environments these tests run in must be as similar as possible to production, to verify that any changes to our environments have not affected the application’s ability to work.

必须尽快收到反馈

The Feedback Must Be Received as Soon as Possible

快速反馈的关键是自动化。使用完全自动化的流程,您唯一的限制是您能够投入解决问题的硬件数量。如果您有手动流程,您将依赖于人来完成工作。人们需要更长的时间,他们会引入错误,而且他们是不可审计的。此外,执行手动构建、测试和部署过程是乏味和重复的——远非人的最佳使用方式。人是昂贵且有价值的,他们应该专注于生产让用户满意的软件,然后尽快交付这些快乐——而不是枯燥、容易出错的任务,如回归测试、虚拟服务器配置和部署,这些任务最好由机器。

The key to fast feedback is automation. With fully automated processes, your only constraint is the amount of hardware that you are able to throw at the problem. If you have manual processes, you are dependent on people to get the job done. People take longer, they introduce errors, and they are not auditable. Moreover, performing manual build, test, and deployment processes is boring and repetitive—far from the best use of people. People are expensive and valuable, and they should be focused on producing software that delights its users and then delivering those delights as fast as possible—not on boring, errorprone tasks like regression testing, virtual server provisioning, and deployment, which are best done by machines.

但是,实施部署管道是资源密集型的,尤其是在您拥有全面的自动化测试套件之后。它的主要目标之一是优化人力资源的使用:我们希望让人们自由地去做有趣的工作,而将重复工作留给机器。

However, implementing a deployment pipeline is resource-intensive, especially once you have a comprehensive automated test suite. One of its key objectives is to optimize for human resource usage: We want to free people to do the interesting work and leave repetition to machines.

我们可以如下描述管道提交阶段的测试(图 1.1)。

We can characterize the tests in the commit stage of the pipeline (Figure 1.1) as follows.

• 他们跑得很快。

• They run fast.

• 它们尽可能全面——也就是说,它们涵盖了超过 75% 左右的代码库,因此当它们通过时,我们对应用程序的工作很有信心。

• They are as comprehensive as possible—that is to say, they cover more than 75% or so of the codebase, so that when they pass, we have a good level of confidence that the application works.

• 如果其中任何一个失败,则意味着我们的应用程序存在严重错误,在任何情况下都不应发布。这意味着检查 UI 元素颜色的测试不应包含在这组测试中。

• If any of them fails, it means our application has a critical fault and should not be released under any circumstances. That means that a test to check the color of a UI element should not be included in this set of tests.

• 它们尽可能与环境无关——也就是说,环境不必是生产的精确复制品,这意味着它可以更简单、更便宜。

• They are as environment-neutral as possible—that is, the environment does not have to be an exact replica of production, which means it can be simpler and cheaper.

另一方面,后期的测试具有以下总体特征。

On the other hand, the tests in the later stages have the following general characteristics.

• 它们运行得更慢,因此适合并行化。

• They run more slowly and therefore are candidates for parallelization.

• 其中一些可能会失败,在某些情况下我们可能仍会选择发布应用程序(也许在发布候选版本中有一个关键修复导致性能低于预定义的阈值——但我们可能会做出发布的决定).

• Some of them may fail, and we may still choose to release the application under some circumstances (perhaps there is a critical fix in the release candidate that causes the performance to drop below a predefined threshold—but we might make the decision to release anyway).

• 它们应该在尽可能类似于生产环境的环境中运行,因此除了测试的直接重点之外,它们还测试部署过程和对生产环境的任何更改。

• They should run on an environment that is as similar as possible to production, so in addition to the direct focus of the test they also test the deployment process and any changes to the production environment.

这种测试过程的组织意味着我们在第一组测试后对软件有很高的信心,它在最便宜的硬件上运行最快。如果这些测试失败,则发布候选版本不会进入后续阶段。这确保了资源的最佳使用。在第 5 章“部署管道剖析”以及随后的第 7、89中有更多关于流水线的内容它们描述了提交测试阶段、自动验收测试和测试非功能性需求。

This organization of the testing process means that we have a high level of confidence in the software after the first set of tests, which run fastest on the cheapest hardware. If these tests fail, the release candidate does not progress to later stages. This ensures optimal use of resources. There is much more on pipelining in Chapter 5, “Anatomy of the Deployment Pipeline,” and the later Chapters 7, 8, and 9 which describe the commit testing stage, automated acceptance testing, and testing nonfunctional requirements.

我们方法的基础之一是需要快速反馈。确保对更改的快速反馈要求我们注意开发软件的过程——特别是我们如何使用版本控制以及我们如何组织代码。开发人员应该经常对他们的版本控制系统进行更改,并将代码拆分为单独的组件,作为管理大型或分布式团队的一种方式。在大多数情况下,应该避免分支。我们在第 13 章“管理组件和依赖性”中讨论增量交付和组件的使用,并在第 14 章“高级版本控制”中讨论分支和合并。

One of the fundamentals of our approach is the need for fast feedback. Ensuring fast feedback on changes requires us to pay attention to the process of developing software—in particular, to how we use version control and how we organize our code. Developers should commit changes to their version control system frequently, and split code into separate components as a way of managing large or distributed teams. Branching should, in most circumstances, be avoided. We discuss incremental delivery and the use of components in Chapter 13, “Managing Components and Dependencies,” and branching and merging in Chapter 14, “Advanced Version Control.”

交付团队必须收到反馈然后采取行动

The Delivery Team Must Receive Feedback and Then Act on It

至关重要的是,参与软件交付过程的每个人都参与反馈过程。这包括开发人员、测试人员、操作人员、数据库管理员、基础设施专家和经理。如果人们在这些角色不会在日常基础上一起工作(尽管我们建议团队应该是跨职能的),但他们必须经常会面并努力改进交付软件的过程。基于持续改进的过程对于快速交付高质量软件至关重要。迭代过程有助于为此类活动建立规律的心跳——每次迭代至少召开一次回顾会议,每个人都会讨论如何改进下一次迭代的交付过程。

It is essential that everybody involved in the process of delivering software is involved in the feedback process. That includes developers, testers, operations staff, database administrators, infrastructure specialists, and managers. If people in these roles do not work together on a day-to-day basis (although we recommend that teams should be cross-functional), it is essential that they meet frequently and work to improve the process of delivering software. A process based on continuous improvement is essential to the rapid delivery of quality software. Iterative processes help establish a regular heartbeat for this kind of activity—at least once per iteration a retrospective meeting is held where everybody discusses how to improve the delivery process for the next iteration.

能够对反馈做出反应也意味着传播信息。使用大的、可见的仪表板(不一定是电子的)和其他通知机制是确保反馈确实得到反馈并使最后一步进入某人头脑的核心。仪表板应该无处不在,当然每个团队房间至少应该有一个。

Being able to react to feedback also means broadcasting information. Using big, visible dashboards (which need not be electronic) and other notification mechanisms is central to ensuring that feedback is, indeed, fed-back and makes the final step into someone’s head. Dashboards should be ubiquitous, and certainly at least one should be present in each team room.

最后,如果不采取行动,反馈是没有用的。这需要纪律和计划。当需要做某事时,整个团队都有责任停止他们正在做的事情并决定行动方案。只有完成后,团队才能继续他们的工作。

Finally, feedback is no good unless it is acted upon. This requires discipline and planning. When something needs doing, it is the responsibility of the whole team to stop what they are doing and decide on a course of action. Only once this is done should the team carry on with their work.

这个过程有规模吗?

Does This Process Scale?

我们听到的一个常见反对意见是,我们描述的过程是理想主义的。这些批评者说,它可能适用于小团队,但它不可能适用于我的大型分布式项目!

One common objection we hear is that the process we describe is idealistic. It may work in small teams, these detractors say, but it can’t possibly work in my huge, distributed project!

多年来,我们在多个不同行业开展过许多大型项目。我们也很幸运能够与拥有丰富经验的同事一起工作。我们在本书中描述的所有技术和原则都已在各种规模的各种组织的各种情况下的实际项目中得到证明。在这样的项目中一次又一次地遇到同样的问题是促使我们写这本书的原因。

We have worked on many large projects over the years in several different industries. We have also been lucky enough to work alongside colleagues with a vast range of experiences. All the techniques and principles that we describe in this book have been proven in real projects in all kinds of organizations, both large and small, in all kinds of situations. Experiencing the same problems over and over again in such projects is what drove us to write this book.

读者会注意到,本书的大部分内容都受到了精益运动的哲学和思想的启发。精益制造的目标是确保快速交付高质量的产品,重点是消除浪费和降低成本。精益制造已在多个行业中节省了大量成本和资源、质量更高的产品以及更快的上市时间。这种哲学也开始成为软件开发领域的主流,它为我们在本书中讨论的大部分内容提供了信息。精益当然不限于其对小型系统的应用。它被创建并应用于大型组织,甚至整个经济体。

Readers will notice that much of this book is inspired by the philosophy and ideas of the lean movement. The goals of lean manufacturing are to ensure the rapid delivery of high-quality products, focusing on the removal of waste and the reduction of cost. Lean manufacturing has resulted in huge cost and resource savings, much higher-quality products, and faster time-to-market in several industries. This philosophy is starting to become mainstream in the field of software development too, and it informs much of what we discuss in this book. Lean is certainly not limited in its application to small systems. It was created and applied to huge organizations, and even whole economies.

理论和实践对于大型团队和小型团队都同样重要,我们的经验是它们是有效的。但是,我们不要求您相信我们所说的。自己试试看就知道了。保留有效的,丢弃无效的,并写下您的经历,以便其他人可以受益。

Both the theory and the practice are as relevant to large teams as they are to small, and our experience has been that they work. However, we don’t ask you to believe what we say. Try it yourself and find out. Keep what works, discard what doesn’t, and write about your experiences so that other people can benefit.

有什么好处?

What Are the Benefits?

我们在上一节中描述的方法的主要好处是它创建了一个可重复、可靠和可预测的发布过程,这反过来会大大缩短周期时间,从而快速为用户提供功能和错误修复。单是节省的成本就不仅抵得上本书的封面价格,还抵得上建立和维护此类发布系统所需的时间投资。

The principal benefit of the approach that we describe in the preceding section is that it creates a release process that is repeatable, reliable, and predictable, which in turn generates large reductions in cycle time, and hence gets features and bugfixes to users fast. The cost savings alone are worth not just the cover price of this book, but also the investment in time that the establishment and maintenance of such a release system entails.

除此之外,还有许多其他好处,其中一些我们会事先预测到,而另一些则更像是我们观察时的惊喜。

Beyond that there are many other benefits, some of which we would have predicted beforehand, while others were more like pleasant surprises when we observed them.

赋能团队

Empowering Teams

部署管道的关键原则之一是它是一个拉动系统——它允许测试人员、操作人员或支持人员自助将他们想要的应用程序版本服务到他们选择的环境中。根据我们的经验,周期时间的主要贡献者是参与交付过程的人员等待获得“良好构建”的应用程序。通常,要获得良好的构建,需要发送无休止的电子邮件、提出罚单或其他低效的沟通方式。当参与交付的团队分散时,这将成为效率低下的主要原因。通过部署管道实施,这个问题就完全消除了——每个人都应该能够看到哪些构建可以部署到他们关心的环境中,并且能够一键执行部署。

One of the key principles of the deployment pipeline is that it is a pull system—it allows testers, operations or support personnel to self-service the version of the application they want into the environment of their choice. In our experience, a major contributor to cycle time is people involved in the delivery process waiting to get a “good build” of the application. Often getting a good build requires endless emails being sent, tickets being raised, or other inefficient forms of communication. When the teams involved in delivery are distributed, this becomes a major source of inefficiency. With a deployment pipeline implementation, this problem is completely removed—everybody should have the ability to see which builds are available to be deployed into the environments they care about and be able to perform a deployment at the push of a button.

我们经常看到的结果是,随着团队的不同成员开展工作,在不同的环境中会出现几个不同的版本。能够轻松地将任何版本的软件部署到任何环境中的能力有很多优势。

What we often see as a result of this is several different versions in play in various environments, as different members of the team go about their work. The ability to easily deploy any version of the software into any environment has many advantages.

• 测试人员可以选择旧版本的应用程序来验证新版本中的行为变化。

• Testers can select older versions of an application to verify changes in behavior in newer versions.

• 支持人员可以将应用程序的已发布版本部署到环境中以重现缺陷。

• Support staff can deploy a released version of the application into an environment to reproduce a defect.

• 作为灾难恢复练习的一部分,运营人员可以选择一个已知的良好构建部署到生产中。

• Operations staff can select a known good build to deploy to production as part of a disaster recovery exercise.

• 按下按钮即可执行释放。

• Releases can be performed at the push of a button.

我们的部署工具为他们提供的灵活性改变了他们的工作方式——变得更好。总的来说,团队成员更能控制他们的工作,因此他们的工作质量得到​​提高,从而使应用程序的质量得到提高。他们更有效地协作,反应更少,并且可以更有效地工作,因为他们不会花太多时间等待将好的构建推送给他们。

The flexibility that our deployment tools offer to them changes the way that they work—for the better. Overall, team members are more in control of their work, and so the quality of their work improves, which makes the quality of the application improve. They collaborate more effectively, are less reactive, and can work more efficiently because they don’t spend so much time waiting for good builds to be pushed to them.

减少错误

Reducing Errors

错误可以从各种各样的地方蔓延到软件中。最初委托软件的人可能会要求错误的东西。捕获需求的分析师可能会误解,开发人员可能会编写有缺陷的代码。不过,我们在这里讨论的错误,特别是那些因配置管理不善而引入生产的错误我们将在第 2 章中更详细地描述配置管理的含义. 现在,想一想使典型应用程序正常工作所必须具备的条件——代码的正确版本,当然,还有数据库模式的正确版本、负载平衡器的正确配置、正确的 URL到您用来查询价格等的 Web 服务。当我们谈论配置管理时,我们指的是允许您识别和控制整套信息的过程和机制,包括每一个最后的位和字节。

Errors can creep into software from all sorts of places. The people who commission the software in the first place can ask for the wrong thing. The analysts who capture the requirements can misunderstand, the developers can write buggy code. The errors we are talking about here, though, are specifically those introduced into production by poor configuration management. We will describe what we mean by configuration management in more detail in Chapter 2. For now, think of the things that have to be just right to make a typical application work—the right version of the code, sure, but also the correct version of the database schema, the correct configuration for load-balancers, the correct URL to that web service that you use to look up prices, and so forth. When we talk about configuration management, we mean the processes and mechanisms that allow you to identify and control that complete set of information, every last bit and byte.

在共同构成现代软件系统的数以千兆字节的信息中,没有机器的帮助,任何人或人类团队都无法发现上文所述示例的规模变化。与其等到问题发生,不如利用机器的帮助,在第一时间阻止它的发生呢?

Of the many gigabytes of information that collectively comprise a modern software system, no human being—or team of human beings—is going to be able to spot a change on the scale of the example described in the preceding sidebar without machine assistance. Instead of waiting until the problem occurs, why not employ the machine assistance to prevent it happening in the first place?

通过主动管理版本控制中可以更改的所有内容——例如配置文件、创建数据库及其模式的脚本、构建脚本、测试工具,甚至开发环境和操作系统配置——我们允许计算机做它们擅长的事情:确保每一个最后的位和字节都在我们期望的位置,至少在我们的代码开始运行之前是这样。

By actively managing everything that can change in version control—such as configuration files, scripts to create databases and their schemas, build scripts, test harnesses, even development environments and operating system configurations—we allow computers to do what they are good at: ensure that every last bit and byte is in the place that we expect it to be, at least up until the point when our code starts running.

根据我们的经验,这种对手动配置管理的依赖很常见。在我们合作过的许多组织中,他们的生产系统和测试环境都是如此。有时服务器 A 将其连接池限制为 100 而服务器 B 将其池设置为 120 可能无关紧要。在其他时候它很重要。

In our experience, this dependence on manual configuration management is common. In many organizations that we have worked with, this is true of both their production systems and their test environments. Sometimes it may not matter that server A has its connection pool limited to 100 while server B has its pool set to 120. At other times it matters a lot.

哪些配置差异重要,哪些不重要,这不是您想要在最繁忙的交易期间偶然发现的。这种配置信息定义了代码运行的环境,实际上经常指定代码的新路径。需要考虑对此类配置信息的更改,代码运行的环境需要像代码本身。如果我们可以访问您的数据库、应用程序服务器或 Web 服务器的配置,我们保证可以使您的应用程序失败的速度比您允许我们访问编译器和您的源代码的速度更快。

Which configuration differences matter and which do not is not something that you want to discover by accident during your busiest trading period. This kind of configuration information defines the environment in which code runs and frequently, in effect, specifies new paths through the code. Changes to such configuration information need to be considered, and the environment in which the code runs needs to be as well defined and controlled as the behavior of the code itself. If we have access to the configuration of your database, application server, or web server, we guarantee that we can make your application fail faster than if you give us access to a compiler and your source code.

当手动定义和管理此类配置参数时,它们会受到人类在重复任务中犯错的倾向的影响。在错误的地方出现一个简单的拼写错误可能会使应用程序停止运行。更糟糕的是,编程语言有语法检查和单元测试来验证没有拼写错误。很少对配置信息进行任何类型的检查,尤其是当配置信息直接输入到某个控制台时。

When such configuration parameters are manually defined and managed, they suffer from the human propensity for making mistakes in repetitive tasks. A simple typo in just the wrong place can stop an application in its tracks. Worse than that, programming languages have syntax checks and perhaps unit tests to verify that there are no typos. There are rarely checks of any kind applied to configuration information, particularly if that configuration information is typed directly into some console.

将配置信息添加到版本控制系统这一简单操作是向前迈出的一大步。最简单的情况是,版本控制系统会提醒您无意中更改了配置。这至少消除了一个非常常见的错误来源。

The simple act of adding your configuration information to your version control system is an enormous step forward. At its simplest, the version control system will alert you to the fact that you have changed the configuration inadvertently. This eliminates at least one very common source of errors.

一旦您的所有配置信息都存储在版本控制系统中,下一个明显的步骤就是消除中间人并让计算机应用配置而不是将其重新输入。有些技术比其他技术更适合于此,但是如果您仔细考虑即使是最棘手的第三方系统的配置,您和基础架构供应商通常会惊讶于您能走多远。我们将在第 4 章稍后部分讨论这方面的细节,并在第 11章详细讨论。

Once all of your configuration information is stored in a version control system, the next obvious step is to eliminate the middleman and get the computer to apply the configuration rather than to type it back in. Some technologies are more amenable to this than others, but you, and often the infrastructure vendors, will be surprised how far you can take this if you think carefully about the configuration of even the most intractable third-party systems. We will discuss the details of this later in Chapter 4, and at length in Chapter 11.

减轻压力

Lowering Stress

在明显的好处中,最令人愉快的是减轻了与发布相关的各方的压力。大多数曾经接触过接近发布日期的软件项目的人都会意识到这些确实是压力大的事件。这本身就是我们经验中问题的根源。我们见过明智、保守、注重质量的项目经理问他们的开发人员,“你不能只修改代码吗?” 或者理智的数据库管理员将数据输入到他们不知道的应用程序的数据库表中。在这两种情况下,以及像他们这样的许多其他人,这种变化都是对“让一些事情发挥作用”的压力的直接回应。

Of the obvious benefits, the most pleasant is the reduction in stress in all parties that are associated with a release. Most people who have ever come anywhere near a software project that is approaching its release date will be aware that these are indeed stressful events. That in itself can be a source of problems in our experience. We have seen sensible, conservative, quality-conscious project managers asking their developers, “Can’t you just modify the code?” or otherwise sane database administrators entering data into tables in databases for applications that they don’t know. On both occasions, and many others like them, the change was in direct response to the pressure to “just get something working.”

不要误会我们的意思,我们也去过那里。我们甚至没有暗示这总是错误的反应:如果您刚刚将一些代码发布到生产环境中导致您的组织流血,那么几乎任何可以阻止流血的方法都是合理的。

Don’t get us wrong, we have been there too. We are not even suggesting that this is always the wrong response: If you have just released some code into production that is causing your organization to bleed money, almost anything that stops the bleed may be justified.

我们的观点不同。这两个让新部署的生产系统运行的快速黑客攻击的例子并不是由这种直接的商业需求驱动的,而是在计划的那一天发布的更微妙的压力。这里的问题是发布到生产环境是一件大事。只要这是真的,他们就会被很多仪式和紧张所包围。

Our point here is different. Both examples of quick hacks to get the newly deployed production system running weren’t being driven by such immediate commercial imperatives, but rather by the more subtle pressure to release on the day that was planned. The problem here is that releases into production are big events. As long as this is true they will be surrounded with a lot of ceremony and nervousness.

暂时想象一下,您即将发布的版本可以通过按一下按钮来执行。想象一下,它可以在几分钟甚至几秒钟内执行,如果发生最坏的情况,您可以在同样的几分钟或几秒钟内撤消发布。想象一下,您经常发布,因此当前正在生产的内容与新版本之间的差异很小。如果那是真的,那么发布的风险就会大大降低,并且你将你的职业生涯押在它的成功上的不愉快感觉也会大大减少。

For a moment, imagine that your upcoming release could be performed with the push of a button. Imagine that it could be performed within a few minutes, or even a few seconds, and that if the worst came to the worst, you could back out the release in the same few minutes or seconds. Imagine that you released frequently, so the delta between what is currently in production and the new release is small. If that were true, then the risk of release would be greatly diminished, and the unpleasant feeling that you are betting your career on its success significantly reduced.

对于一小部分项目,这个理想可能无法实际实现。然而,在大多数项目中确实如此,尽管需要付出一定程度的努力。减轻压力的关键是拥有我们所描述的那种自动化部署过程,经常执行它,并在涉及到您在最坏情况发生时支持更改的能力时有一个好故事。第一次做自动化,会很痛苦——但会变得更容易,对项目和对自己的好处几乎无法估量。

For a small set of projects, this ideal may not be practically achievable. However, in most projects it certainly is, albeit with some degree of effort. The key to reducing stress is to have the kind of automated deployment process that we have described, to perform it frequently, and to have a good story when it comes to your ability to back changes out should the worst happen. The first time you do automation, it will be painful—but it will become easier, and the benefits to the project and to yourself are almost incalculably large.

部署灵活性

Deployment Flexibility

在新环境中启动您的应用程序应该是一项简单的任务——理想情况下只需调试机器或虚拟映像并创建一些描述环境独特属性的配置信息。然后您应该能够使用您的自动化部署过程来准备新的部署环境并将您的应用程序的选定版本部署到它。

It should be a simple task to start your application in a new environment—ideally just a matter of commissioning the machines or virtual images and creating some configuration information that describes the environment’s unique properties. Then you should be able to use your automated deployment process to prepare the new environment for deployment and deploy the chosen version of your application to it.

熟能生巧

Practice Makes Perfect

在我们从事的项目中,我们试图为每个开发人员或一对开发人员实现专用的开发环境。然而,即使在没有走那么远的项目中,任何使用持续集成或迭代、增量开发技术的团队都需要经常部署应用程序。

In projects we work on, we try to achieve a dedicated development environment for each developer or pair of developers. However, even in projects that don’t take it that far, any team that uses continuous integration or iterative, incremental development techniques will need to deploy the application frequently.

最好的策略是无论部署目标是什么,都使用相同的部署方法。不应有特殊的 QA 部署策略,或特殊的验收测试或生产部署策略。这样,每次部署应用程序时,我们都在确认我们的部署机制是否正常工作。本质上,每次将软件部署到任何目标时,都会排练最终部署到生产中。

The best strategy to adopt is to use the same deployment approach whatever the deployment target. There should not be a special QA deployment strategy, or a special acceptance test, or production deployment strategy. In this way every time the application is deployed, we are confirming that our deployment mechanism is working correctly. In essence, the final deployment into production is being rehearsed every single time the software is deployed to any target.

有一种特殊情况允许一些变化:开发环境。开发人员需要构建二进制文件而不是采用在其他地方构建的预先准备好的二进制文件是有道理的,因此对于这些部署可以放宽此限制。不过,即使在开发人员工作站上,我们也会尽可能多地尝试以相同的方式部署和管理事物。

There is one special case where some variation is permissible: the development environment. It makes sense that the developers will need to build binaries rather than take pre-prepared binaries built elsewhere, so this constraint can be relaxed for those deployments. Even on developer workstations, though, we try as much as possible to deploy and manage things in the same way.

发布候选

The Release Candidate

什么是候选发布对代码的更改可能会或可能不会发布。如果您要查看更改并问:“我们应该发布此更改吗?” 那么答案只能是猜测。正是我们应用于该变更的构建、部署和测试过程验证了该变更是否可以发布。这个过程使我们越来越有信心可以安全地发布更改。我们接受那个小的变化——无论是新功能、错误修复,还是重新调整系统以实现性能上的一些变化——并验证我们是否可以高度自信地发布带有该变化的系统。为了进一步降低风险,我们希望在尽可能短的时间内执行此验证。

What is a release candidate? A change to your code may or may not be releasable. If you were to look at a change and ask, “Should we release this change?” then the answer could only be a guess. It is the build, deployment, and test process that we apply to that change that validates whether the change can be released. This process gives us increasing confidence that the change is safe to release. We take that small change—whether it is new functionality, a bugfix, or a retuning of the system to achieve some change in performance—and verify whether or not we can release the system with that change with a high level of confidence. In order to reduce the risk further, we want to perform this validation in the shortest possible time.

虽然任何更改都可能导致可以发布给用户的工件,但他们不会以这种方式开始。必须评估每个更改的适用性。如果发现最终产品没有缺陷,并且符合客户规定的验收标准,则可以放行。

While any change may lead to an artifact that can be released to users, they don’t start off that way. Every change must be evaluated for its fitness. If the resulting product is found to be free of defects, and it meets the acceptance criteria set out by the customer, then it can be released.

图 1.2 候选发布版本的传统视图

Figure 1.2 Traditional view of release candidates

图片

大多数发布软件的方法都会在流程结束时识别发布候选者。当有与跟踪相关的工作时,这是有道理的。在撰写本文时,描述开发阶段的维基百科条目将“候选发布”显示为流程中的一个独特步骤(图 1.2)。我们对事物的看法有所不同。

Most approaches to releasing software identify release candidates at the end of the process. This makes some sense when there is work associated with the tracking. At the time of writing, the Wikipedia entry describing development stages shows “release candidate” as a distinct step in the process (Figure 1.2). We see things a little differently.

传统的软件开发方法会延迟发布候选版本的提名,直到采取几个冗长且昂贵的步骤来确保软件具有足够的质量和功能完整。然而,在积极追求构建和部署自动化以及全面自动化测试的环境中,没有必要在项目结束时花费时间和金钱进行冗长的手动密集型测试。这个阶段应用的质量通常会明显提高,所以手动测试只是对功能完整性的肯定。

Traditional approaches to software development delay the nomination of a release candidate until several lengthy and expensive steps have been taken to ensure that the software is of sufficient quality and functionally complete. However, in an environment where build and deployment automation is aggressively pursued along with comprehensive automated testing, there is no need to spend time and money on lengthy and manually intensive testing at the end of the project. At this stage the quality of the application is usually significantly higher, so manual testing is only an affirmation of the functional completeness.

事实上,根据我们的经验,将测试推迟到开发过程之后是降低应用程序质量的必经之路。缺陷最好在引入的地方被发现和修复。当它们后来被发现时,修复它们的成本总是更高。开发人员已经忘记了他们引入缺陷时他们在做什么,并且功能可能在此期间发生了变化。将测试留到最后通常意味着没有时间真正修复错误,或者只能修复其中的一小部分。因此,我们希望尽早找到并修复它们,最好是将它们签入代码之前。

Indeed, delaying testing until after the development process is, in our experience, a sure-fire way to decrease the quality of your application. Defects are best discovered and fixed at the point where they are introduced. When they are discovered later, they are always more expensive to fix. The developers have forgotten what they were doing at the time when they introduced the defect, and the functionality may have changed in the meantime. Leaving testing until the end normally means that there is no time to actually fix the bugs, or that only a small proportion of them can be fixed. So we want to find and fix them at the earliest possible opportunity, preferably before they are checked in to the code.

每次签到都会导致潜在的释放

Every Check-in Leads to a Potential Release

开发人员对代码库所做的每一次更改都旨在以某种方式增加价值。提交给版本控制的每个更改都应该增强我们正在处理的系统。我们怎么知道这是不是真的?我们可以判断的唯一方法是通过运行软件来查看它是否达到了我们预期的价值。大多数项目将这部分过程推迟到在开发中的功能的生命后期。这意味着,就任何人所知,系统在测试或使用时发现它可以正常工作之前都是坏掉的。如果此时发现它坏了,通常需要大量工作才能使系统正常运行。此阶段通常被描述为集成,通常是开发过程中最不可预测和最难以管理的部分。由于它是如此痛苦,团队推迟了它,很少集成,这只会让情况变得更糟。

Every change that a developer makes to a codebase is intended to add value in some manner. Every change committed to version control is supposed to enhance the system that we are working on. How do we know if that is true? The only way in which we can tell is through exercising the software to see if it achieves the value that we had expected. Most projects defer this part of the process until later in the life of the feature under development. This means that as far as anybody knows, the system is broken until it is found to be working when it is tested or used. If it is found to be broken at this point, it usually takes a significant amount of work to get the system running as it should. This phase is usually described as integration and is often the most unpredictable and unmanageable part of the development process. Since it is so painful, teams defer it, integrating infrequently, which only makes it worse.

在软件中,当某些事情很痛苦时,减轻痛苦的方法是更频繁地做这件事,而不是更少。所以与其不频繁地集成,不如频繁地集成;事实上,我们应该将系统的每一个变化作为结果进行整合。这种持续集成的实践将频繁集成的想法发挥到了逻辑上的极致。在这样做的过程中,它在软件开发过程中创造了范式转变。持续集成检测任何破坏系统或在将其引入系统时不满足客户验收标准的更改。一旦问题发生,团队就会立即解决问题(这是持续集成的第一条规则)。当遵循这种做法时,软件总是处于工作状态。如果您的测试足够全面,并且您在充分类似于生产的环境中运行测试,那么该软件实际上始终处于发布状态。

In software, when something is painful, the way to reduce the pain is to do it more frequently, not less. So instead of integrating infrequently, we should integrate frequently; in fact, we should integrate as a result of every single change to the system. This practice of continuous integration takes the idea of integrating frequently to its logical extreme. In doing so, it creates a paradigm shift in the software development process. Continuous integration detects any change that breaks the system or does not fulfill the customer’s acceptance criteria at the time it is introduced into the system. Teams then fix the problem as soon as it occurs (this is the first rule of continuous integration). When this practice is followed, then the software is always in a working state. If your tests are sufficiently comprehensive and you are running tests on a sufficiently production-like environment, then the software is in fact always in a releasable state.

实际上,每个更改都是候选发布版本。每次将更改提交到版本控制时,都希望它能通过所有测试、生成工作代码并可以发布到生产环境中。这是开始的假设。持续集成系统的工作是反驳这个假设,表明特定的候选发布版本不适合将其投入生产。

Every change is, in effect, a release candidate. Every time a change is committed to version control, the expectation is that it will pass all of its tests, produce working code, and can be released into production. This is the starting assumption. The job of a continuous integration system is to disprove this assumption, to show that a particular release candidate is not fit to make it into production.

软件交付原则

Principles of Software Delivery

本书背后的想法来源于作者多年来从事的大量项目。当我们开始综合我们的想法并将它们记录在这些页面中的活动时,我们注意到相同的原则一遍又一遍地出现。我们在这里列举了它们。我们所说的某些内容可能会受到解释或警告;下面的原则不是。如果我们希望我们的交付过程有效,这些是我们无法想象没有的事情。

The ideas behind this book were informed by a large number of projects that the authors have worked on over a period of many years. As we commenced the activity of synthesizing our thoughts and capturing them in these pages, we noticed that the same principles came up over and over again. We’ve enumerated them here. Some of what we say is subject to interpretation or caveats; the principles below are not. These are the things that we can’t imagine doing without if we want our delivery process to be effective.

为发布软件创建可重复、可靠的流程

Create a Repeatable, Reliable Process for Releasing Software

这个原则实际上是我们写这本书的目的的陈述:发布软件应该很容易。这应该很容易,因为您之前已经对发布过程的每个部分进行了数百次测试。它应该像按下按钮一样简单。可重复性和可靠性源自两个原则:自动化几乎所有内容,并在版本控制中保留构建、部署、测试和发布应用程序所需的一切。

This principle is really a statement of our aim in writing this book: Releasing software should be easy. It should be easy because you have tested every single part of the release process hundreds of times before. It should be as simple as pressing a button. The repeatability and reliability derive from two principles: automate almost everything, and keep everything you need to build, deploy, test, and release your application in version control.

部署软件最终涉及三件事:

Deploying software ultimately involves three things:

• 供应和管理您的应用程序运行的环境(硬件配置、软件、基础设施和外部服务)。

• Provisioning and managing the environment in which your application will run (hardware configuration, software, infrastructure, and external services).

• 将正确版本的应用程序安装到其中。

• Installing the correct version of your application into it.

• 配置您的应用程序,包括它需要的任何数据或状态。

• Configuring your application, including any data or state it requires.

您的应用程序的部署可以使用来自版本控制的完全自动化的过程来实现。应用程序配置也可以是一个完全自动化的过程,必要的脚本和状态保存在版本控制或数据库中。显然,硬件不能保留在版本控制中;但是,特别是随着 Puppet 等廉价虚拟化技术和工具的出现,配置过程也可以完全自动化。

The deployment of your application can be implemented using a fully automated process from version control. Application configuration can also be a fully automated process, with the necessary scripts and state kept in version control or databases. Clearly, hardware cannot be kept in version control; but, particularly with the advent of cheap virtualization technology and tools like Puppet, the provisioning process can also be fully automated.

本书的其余部分基本上描述了实现这一原则的策略。

The rest of this book essentially describes strategies for realizing this principle.

自动化几乎一切

Automate Almost Everything

有些事情是不可能自动化的。探索性测试依赖于经验丰富的测试人员。无法通过计算机向您的用户社区的代表演示工作软件。根据定义,合规性批准需要人为干预。然而,无法自动化的事情清单比许多人想象的要少得多。一般来说,您的构建过程应该是自动化的,直到它需要特定的人工指导或决策制定。您的部署过程也是如此,事实上,您的整个软件发布过程也是如此。验收测试可以自动化。数据库升级和降级也可以自动化。甚至网络和防火墙配置也可以自动化。您应该尽可能地自动化。

There are some things it is impossible to automate. Exploratory testing relies on experienced testers. Demonstrations of working software to representatives of your user community cannot be performed by computers. Approvals for compliance purposes by definition require human intervention. However, the list of things that cannot be automated is much smaller than many people think. In general, your build process should be automated up to the point where it needs specific human direction or decision making. This is also true of your deployment process and, in fact, your entire software release process. Acceptance tests can be automated. Database upgrades and downgrades can be automated too. Even network and firewall configuration can be automated. You should automate as much as you possibly can.

您的作者可以诚实地说,他们还没有找到无法通过足够的工作和独创性实现自动化的构建或部署过程。

Your authors can honestly say that they haven’t found a build or deployment process that couldn’t be automated with sufficient work and ingenuity.

大多数开发团队不会自动化他们的发布过程,因为这似乎是一项艰巨的任务。手动做事更容易。也许在他们第一次执行流程中的某个步骤时是这样,但是当他们第十次执行该步骤时肯定不是这样,而且当他们执行三四次时可能就不是这样了。

Most development teams don’t automate their release process because it seems such a daunting task. It’s easier just to do things manually. Perhaps that is true the first time they perform a step in the process, but it is certainly not true by the time they perform that step for the tenth time, and is probably not true by the time they have done it three or four times.

自动化是部署管道的先决条件,因为只有通过自动化,我们才能保证人们只需按一下按钮就能得到他们需要的东西。但是,您不需要立即自动化所有内容。您应该首先查看构建、部署、测试和发布过程中当前存在瓶颈的部分。随着时间的推移,您可以而且应该逐渐实现自动化。

Automation is a prerequisite for the deployment pipeline, because it is only through automation that we can guarantee that people will get what they need at the push of a button. However, you don’t need to automate everything at once. You should start by looking at that part of your build, deploy, test, and release process that is currently the bottleneck. You can, and should, automate gradually over time.

将所有内容保存在版本控制中

Keep Everything in Version Control

构建、部署、测试和发布应用程序所需的一切都应保存在某种形式的版本化存储中。这包括需求文档、测试脚本、自动化测试用例、网络配置脚本、部署脚本、数据库创建、升级、降级和初始化脚本、应用程序堆栈配置脚本、库、工具链、技术文档等。所有这些东西都应该是版本控制的,并且相关版本对于任何给定的构建都应该是可识别的。也就是说,这些变更集应该有一个单一的标识符,例如构建号或版本控制变更集号,它引用了每个部分。

Everything you need to build, deploy, test, and release your application should be kept in some form of versioned storage. This includes requirement documents, test scripts, automated test cases, network configuration scripts, deployment scripts, database creation, upgrade, downgrade, and initialization scripts, application stack configuration scripts, libraries, toolchains, technical documentation, and so on. All of this stuff should be version-controlled, and the relevant version should be identifiable for any given build. That is, these change sets should have a single identifier, such as a build number or a version control change set number, that references every piece.

新的团队成员应该可以坐在新的工作站前,检查项目的修订控制存储库,并运行单个命令来构建应用程序并将其部署到任何可访问的环境,包括本地开发工作站。

It should be possible for a new team member to sit down at a new workstation, check out the project’s revision control repository, and run a single command to build and deploy the application to any accessible environment, including the local development workstation.

还应该可以查看将各种应用程序的哪个版本部署到每个环境中,以及这些版本来自版本控制中的哪些版本。

It should also be possible to see which build of your various applications is deployed into each of your environments, and which versions in version control these builds came from.

如果它痛,就更频繁地做,并将疼痛提前

If It Hurts, Do It More Frequently, and Bring the Pain Forward

这是我们列表中最普遍的原则,或许最好将其描述为一种启发式原则。但它可能是我们所知道的在交付软件的上下文中最有用的启发式方法,它会影响我们所说的一切。整合往往是一个非常痛苦的过程。如果这在您的项目中是正确的,那么在每次有人签入时进行集成,并从项目一开始就这样做。如果测试是发布前发生的痛苦过程,请不要在最后进行。相反,从项目开始就不断地这样做。

This is the most general principle on our list, and could perhaps best be described as a heuristic. But it is perhaps the most useful heuristic we know of in the context of delivering software, and it informs everything we say. Integration is often a very painful process. If this is true on your project, integrate every time somebody checks in, and do it from the start of the project. If testing is a painful process that occurs just before release, don’t do it at the end. Instead, do it continually from the beginning of the project.

如果发布软件很痛苦,那么就在每次有人签入通过所有自动化测试的更改时发布它。如果您不能在每次更改时将其发布给真实用户,请在每次签入时将其发布到类似生产的环境中。如果创建应用程序文档很痛苦,请在开发新功能时进行,而不是留到最后再做。为完成定义的功能部分制作文档,并尽可能自动化该过程。

If releasing software is painful, aim to release it every time somebody checks in a change that passes all the automated tests. If you can’t release it to real users upon every change, release it to a production-like environment upon every check-in. If creating application documentation is painful, do it as you develop new features instead of leaving it to the end. Make documentation for a feature part of the definition of done, and automate the process as far as possible.

根据您当前的专业水平,可能需要付出大量努力才能实现这一目标,当然您仍然必须同时交付软件。以中期目标为目标,例如每隔几周发布一次内部版本,或者如果您已经在这样做,则每周发布一次。逐渐努力接近理想——即使是小步骤也会带来巨大的好处。

Depending on your current level of expertise, it could take a serious amount of effort to reach this goal, and of course you still have to deliver software in the meantime. Aim for intermediate goals, such as an internal release every few weeks or, if you’re already doing that, every week. Gradually work to approach the ideal—even small steps will deliver great benefits.

极限编程本质上是将这种启发式方法应用于软件开发过程的结果。本书中的大部分建议都来自我们将相同原则应用于软件发布过程的经验。

Extreme programming is essentially the result of applying this heuristic to the software development process. Much of the advice in this book comes from our experience of applying the same principle to the process of releasing software.

建立质量

Build Quality In

这个原则和我们在本节中提到的最后一个原则——持续改进——是无耻地从精益运动中偷来的。“建立质量”是 W. Edwards Deming 的座右铭,他是精益运动的先驱之一。越早发现缺陷,修复它们的成本就越低。如果从一开始就没有将缺陷签入版本控制,那么修复缺陷的成本最低。

This principle and the last one we mention in this section—continuous improvement—are shamelessly stolen from the lean movement. “Build quality in” was the motto of W. Edwards Deming who was, among his other distinctions, one of the pioneers of the lean movement. The earlier you catch defects, the cheaper they are to fix. Defects are fixed most cheaply if they are never checked in to version control in the first place.

我们在本书中描述的技术,如持续集成、全面的自动化测试和自动化部署,旨在尽可能早地在交付过程中发现缺陷(“带来痛苦”原则的应用)。下一步是修复它们。如果每个人都忽视火警警报,它就毫无用处。交付团队必须在发现缺陷后立即修复缺陷。

The techniques that we describe in this book, such as continuous integration, comprehensive automated testing, and automated deployment, are designed to catch defects as early in the delivery process as possible (an application of the principle “Bring the pain forward”). The next step is to fix them. A fire alarm is useless if everybody ignores it. Delivery teams must be disciplined about fixing defects as soon as they are found.

“构建质量”还有另外两个推论。首先,测试不是一个阶段,当然也不是在开发阶段之后才开始的。如果测试留到最后,那就太晚了。将没有时间修复缺陷。其次,测试也不是纯粹或主要是测试人员的领域。交付团队中的每个人始终对应用程序的质量负责。

There are two other corollaries of “Build quality in.” Firstly, testing is not a phase, and certainly not one to begin after the development phase. If testing is left to the end, it will be too late. There will be no time to fix the defects. Secondly, testing is also not the domain, purely or even principally, of testers. Everybody on the delivery team is responsible for the quality of the application all the time.

完成意味着发布

Done Means Released

您有多少次听到开发人员说故事或功能“完成”了?也许您听说过项目经理问开发人员是否“完成了”?“完成”是什么意思?实际上,一项功能只有在为用户提供价值时才会完成。这是持续部署实践背后的部分动机(请参阅第 10 章,“部署和发布应用程序”)。

How often have you heard a developer say a story or feature is “done”? Perhaps you have heard a project manager asking that developer if it is “done done”? What does “done” mean? Really, a feature is only done when it is delivering value to users. This is part of the motivation behind the practice of continuous deployment (see Chapter 10, “Deploying and Releasing Applications”).

对于一些敏捷交付团队来说,“完成”意味着发布到生产环境中。这是软件开发项目的理想情况。但是,将其用作完成的衡量标准并不总是可行的。软件系统的初始版本可能需要一段时间才能处于真正的外部用户可以从中受益的状态。因此,我们将回拨到下一个最佳选项,并说一旦成功展示功能就“完成”,也就是说,从类似生产的环境中向用户社区的代表展示并由其试用。

For some agile delivery teams, “done” means released into production. This is the ideal situation for a software development project. However, it is not always practical to use this as a measure of done. The initial release of a software system can take a while before it is in a state where real external users are getting benefit from it. So we will dial back to the next best option and say that a functionality is “done” once it has been successfully showcased, that is, demonstrated to, and tried by, representatives of the user community, from a production-like environment.

没有“80% 完成”。事情要么完成,要么不完成。有可能在某事完成之前估计剩余的工作——但这​​些永远只是估计。使用估算值来确定剩余工作总量会导致相互指责和相互指责,因为当引用百分比的人总是错误的时候,他们总是会这样做。

There is no “80% done.” Things are either done, or they are not. It is possible to estimate the work remaining before something is done—but those will only ever be estimates. Using an estimate to determine the total amount of remaining work leads to recriminations and finger-pointing when those quoting the percentage turn out, as they invariably do, to be wrong.

这个原则有一个有趣的推论:完成某事不是一个人的力量。它需要交付团队中的许多人一起工作才能完成任何事情。这就是为什么每个人——测试人员、构建和操作人员、支持团队、开发人员——一起工作如此重要从最开始。这也是整个交付团队负责交付的原因——这一原则非常重要,以至于它有自己的一部分……

This principle has an interesting corollary: It is not in the power of one person to get something done. It requires a number of people on a delivery team to work together to get anything done. That’s why it’s so important for everybody—testers, build and operations personnel, support teams, developers—to work together from the beginning. It’s also why the whole delivery team is responsible for delivering—a principle so important that it gets a section of its own...

交付过程人人有责

Everybody Is Responsible for the Delivery Process

理想情况下,组织内的每个人都与其目标保持一致,人们共同努力帮助每个人实现目标。最终团队的成功或失败是作为一个团队,而不是作为个人。然而,在太多的项目中,现实情况是开发人员将他们的工作扔给了测试人员。然后,测试人员在发布时将工作越过墙交给运营团队。当出现问题时,人们会花很多时间互相指责,因为他们会花很多时间来修复这种孤立的方法不可避免地产生的缺陷。

Ideally, everybody within an organization is aligned with its goals, and people work together to help each to meet them. Ultimately the team succeeds or fails as a team, not as individuals. However, in too many projects the reality is that developers throw their work over the wall to testers. Then testers throw work over the wall to the operations team at release time. When something goes wrong, people spend as much time blaming one another as they do fixing the defects that inevitably arise from such a siloed approach.

如果您在小型组织或相对独立的部门工作,您可能完全控制发布软件所需的资源。如果是这样,太棒了。否则,实现这一原则可能需要长时间的艰苦努力,才能打破将担任不同角色的人隔离开来的孤岛之间的壁垒。

If you are working in a small organization or in a relatively independent department, you may have complete control over the resources that you need to release software. If so, fantastic. If not, realizing this principle may require hard work over a long period of time to break down the barriers between the silos that isolate people in different roles.

从新项目开始就让每个人都参与到交付过程中,并确保他们有机会定期进行交流。一旦障碍消除,这种沟通应该持续进行,但您可能需要逐步朝着该目标迈进。启动一个系统,让每个人都能一目了然地看到应用程序的状态、它的健康状况、它的各种构建、它们通过了哪些测试,以及它们可以部署到的环境的状态。该系统还应该使人们能够执行他们完成工作所需的操作,例如部署到他们控制的环境中。

Start by getting everybody involved in the delivery process together from the start of a new project, and ensure that they have an opportunity to communicate on a frequent regular basis. Once the barriers are down, this communication should occur continuously, but you may need to move towards that goal incrementally. Initiate a system where everyone can see, at a glance, the status of the application, its health, its various builds, which tests they have passed, and the state of the environments they can be deployed to. This system should also make it possible for people to perform the actions that they need to do their job, such as deployment to environments under their control.

这是 DevOps 运动的核心原则之一。DevOps 运动的重点是我们在本书中设定的相同目标:鼓励参与软件交付的每个人之间加强协作,以便更快、更可靠地发布有价值的软件 [aNgvoV]。

This is one of the central principles of the DevOps movement. The DevOps movement is focused on the same goal we set out in this book: encouraging greater collaboration between everyone involved in software delivery in order to release valuable software faster and more reliably [aNgvoV].

连续的提高

Continuous Improvement

值得强调的是,应用程序的首次发布只是其生命周期的第一个阶段。所有应用程序都在发展,更多版本将随之而来。重要的是您的交付过程也随之发展。

It is worth emphasizing that the first release of an application is just the first stage in its life. All applications evolve, and more releases will follow. It is important that your delivery process also evolves with it.

整个团队应该定期聚在一起,对交付过程进行回顾。这意味着团队应该反思什么做得好,什么做得不好,并讨论如何改进的想法。应该指定某人负责每个想法并确保将其付诸实践。然后,下次团队聚集时,他们应该报告发生的事情。这就是众所周知的戴明循环:计划、执行、研究、行动。

The whole team should regularly gather together and hold a retrospective on the delivery process. This means that the team should reflect on what has gone well and what has gone badly, and discuss ideas on how to improve things. Somebody should be nominated to own each idea and ensure that it is acted upon. Then, the next time that the team gathers, they should report back on what happened. This is known as the Deming cycle: plan, do, study, act.

组织中的每个人都参与此过程至关重要。允许反馈只发生在筒仓内而不是跨越筒仓是一个灾难的秘诀:它以牺牲一般优化为代价导致局部优化——最终导致相互指责。

It is essential that everybody in the organization is involved in this process. Allowing feedback to happen only within silos and not across them is a recipe for disaster: It leads to local optimization at the expense of general optimization—and, ultimately, finger-pointing.

概括

Summary

传统上,软件发布的时间点充满了压力。同时,与代码创建和管理相关的规程相比,它被视为未经验证的手动过程,在系统配置的关键方面依赖于临时配置管理技术。在我们看来,与软件发布相关的压力及其手动、容易出错的性质是相关因素。

Traditionally, the point of software release has been a time fraught with stress. At the same time, when compared to the disciplines associated with creation and management of code, it is treated as an unverified, manual process that relies on ad-hoc configuration management techniques for crucial aspects of the configuration of the system. In our view, the stress associated with software releases and their manual, error-prone nature are related factors.

通过采用自动化构建、测试和部署技术,我们获得了许多好处。我们获得了验证更改的能力,使流程可在各种环境中重现,并在很大程度上消除了错误进入生产的机会。我们获得了部署变更的能力,从而更快地带来业务收益,因为发布过程本身不再是障碍。实施自动化系统鼓励我们实施其他良好实践,例如行为驱动开发和综合配置管理。

By adopting the techniques of automated build, test, and deployment, we gain many benefits. We gain the ability to verify changes, to make the process reproducible across a range of environments, and to largely eliminate the opportunity for errors to creep into production. We gain the ability to deploy changes, and so bring business benefits more quickly, because the release process itself is no longer a hurdle. Implementing an automated system encourages us to implement other good practices, such as behavior-driven development and comprehensive configuration management.

我们也有能力与家人和朋友共度更多周末,减轻生活压力,同时提高工作效率。有什么不喜欢的?人生苦短,无法在服务器机房中度过周末来部署应用程序。

We also gain the ability to spend more weekends with our families and friends and to live our lives with less stress, while at the same time being more productive. What is not to like about that? Life is too short to spend our weekends in server rooms deploying applications.

开发、测试和发布过程的自动化对发布软件的速度、质量和成本有着深远的影响。其中一位作者在复杂的分布式系统上工作。将此系统发布到生产环境(包括大型数据库中的数据迁移)需要 5 到 20 分钟,具体取决于与特定发布相关的数据迁移规模。移动数据需要很长时间。据我们所知,一个密切相关且具有可比性的系统需要 30 天才能完成该过程的同一部分。

The automation of the development, test, and release processes has a profound impact on the speed, quality, and cost of releasing software. One of the authors works on a complex distributed system. Release into production for this system, including data migration in large-scale databases, takes between 5 and 20 minutes depending on the scale of the data migration associated with a particular release. Moving the data takes a long time. A closely related, and comparable, system of which we are aware takes 30 days for the same part of the process.

本书的其余部分将在我们提供的建议和我们提出的建议中更加具体,但我们希望本章为您提供一个理想的,但仍然是现实的,从两万英尺的角度看待本书的范围。我们在这里提到的项目都是真实的项目,虽然我们可能对它们进行了一些掩饰以保护有罪的人,但我们非常努力地不夸大任何技术细节或任何技术的价值。

The rest of this book will be more concrete in the advice that we offer and the recommendations that we make, but we wanted this chapter to give you an ideal, but nevertheless realistic, view of the scope of this book—from twenty thousand feet. The projects that we have referred to here are all real projects, and while we may have disguised them a little to protect the guilty, we have tried very hard not to exaggerate any technical detail or the value of any technique.

第 2 章配置管理

Chapter 2. Configuration Management

介绍

Introduction

配置管理是一个被广泛使用的术语,通常作为版本控制的同义词。值得用我们自己的非正式定义来设置本章的上下文:

Configuration management is a term that is widely used, often as a synonym for version control. It is worth setting the context for this chapter with our own informal definition:

配置管理是指存储、检索、唯一标识和修改与项目相关的所有工件以及它们之间的关系的过程。

Configuration management refers to the process by which all artifacts relevant to your project, and the relationships between them, are stored, retrieved, uniquely identified, and modified.

您的配置管理策略将决定您如何管理项目中发生的所有更改。因此,它记录了您的系统和应用程序的演变。它还将管理您的团队如何协作——这是任何配置管理策略的重要但有时被忽视的结果。

Your configuration management strategy will determine how you manage all of the changes that happen within your project. It thus records the evolution of your systems and applications. It will also govern how your team collaborates—a vital but sometimes overlooked consequence of any configuration management strategy.

尽管版本控制系统是配置管理中最明显的工具,但决定使用一个(每个团队都应该使用一个,无论多小)只是制定配置管理策略的第一步。

Although version control systems are the most obvious tool in configuration management, the decision to use one (and every team should use one, no matter how small) is just the first step in developing a configuration management strategy.

最后,如果您有一个好的配置管理策略,您应该能够对以下所有问题回答“是”:

Ultimately, if you have a good configuration management strategy, you should be able to answer “yes” to all of the following questions:

• 我能否准确地重现我的任何环境,包括操作系统版本、补丁级别、网络配置、软件堆栈、部署到其中的应用程序及其配置?

• Can I exactly reproduce any of my environments, including the version of the operating system, its patch level, the network configuration, the software stack, the applications deployed into it, and their configuration?

• 我能否轻松地对这些单个项目中的任何一项进行增量更改并将更改部署到我的任何或所有环境中?

• Can I easily make an incremental change to any of these individual items and deploy the change to any, and all, of my environments?

• 我能否轻松查看特定环境发生的每项更改并追溯以准确了解更改内容、更改人员以及更改时间?

• Can I easily see each change that occurred to a particular environment and trace it back to see exactly what the change was, who made it, and when they made it?

• 我能否满足我所遵守的所有合规规定?

• Can I satisfy all of the compliance regulations that I am subject to?

• 团队中的每个成员是否容易获得他们需要的信息,并做出他们需要做出的改变?或者该策略是否妨碍了高效交付,导致周期时间增加和反馈减少?

• Is it easy for every member of the team to get the information they need, and to make the changes they need to make? Or does the strategy get in the way of efficient delivery, leading to increased cycle time and reduced feedback?

最后一点很重要,因为我们经常遇到配置管理策略,它解决了前四点,但却在团队之间的协作方式中设置了各种障碍。这是不必要的——如果足够小心,这最后一个约束不需要与其他约束相对立。在本章中,我们不会告诉你如何回答所有这些问题,尽管我们在本书的整个过程中都会解决这些问题。在本章中,我们将问题分为三个部分:

The last point is important, as we all too often encounter configuration management strategies which address the first four points but put all kinds of barriers in the way of collaboration between teams. This is unnecessary—with sufficient care, this last constraint does not need to be antithetical to the others. We don’t tell you how to answer all of these questions in this chapter, although we do address them all through the course of this book. In this chapter, we divide the problem into three:

1. 准备好管理应用程序构建、部署、测试和发布过程的先决条件。我们分两部分解决这个问题:将所有内容纳入版本控制和管理依赖项。

1. Getting the prerequisites in place to manage your application’s build, deploy, test, and release process. We tackle this in two parts: getting everything into version control and managing dependencies.

2. 管理应用程序的配置。

2. Managing an application’s configuration.

3. 整个环境的配置管理——应用程序所依赖的软件、硬件和基础设施;环境管理背后的原则,从操作系统到应用程序服务器、数据库和其他商业现成 (COTS) 软件。

3. Configuration management of whole environments—the software, hardware, and infrastructure that an application depends upon; the principles behind environment management, from operating systems to application servers, databases, and other commercial off-the-shelf (COTS) software.

使用版本控制

Using Version Control

版本控制系统,也称为源代码控制、源代码管理系统或版本控制系统,是一种保留文件多个版本的机制,因此当您修改文件时,您仍然可以访问以前的版本。它们也是参与软件交付的人员协作的一种机制。

Version control systems, also known as source control, source code management systems, or revision control systems, are a mechanism for keeping multiple versions of your files, so that when you modify a file you can still access the previous revisions. They are also a mechanism through which people involved in software delivery collaborate.

第一个流行的版本控制系统是一个专有的 UNIX 工具,称为 SCCS(源代码控制系统),可以追溯到 1970 年代。这被 RCS(版本控制系统)和后来的 CVS(并发版本系统)所取代。这三种系统今天仍在使用,尽管市场份额越来越小。现在有许多更好的版本控制系统,包括开源的和专有的,专为各种不同的环境而设计。尤其是,我们相信很少有开源工具(Subversion、Mercurial 或 Git)不能满足大多数团队需求的情况。在第 14 章“高级版本控制”中,我们将花更多时间探索版本控制系统和使用它们的模式,包括分支和合并。

The first popular version control system was a proprietary UNIX tool called SCCS (Source Code Control System) which dates back to the 1970s. This was superseded by RCS, the Revision Control System, and later CVS, Concurrent Versions System. All three of these systems are still in use today, although with an increasingly small market share. Nowadays there is a wealth of better version control systems, both open source and proprietary, designed for a variety of different environments. In particular, we believe that there are few circumstances in which the open source tools—Subversion, Mercurial, or Git—would not satisfy most teams’ requirements. We will spend much more time exploring version control systems and patterns for using them, including branching and merging, in Chapter 14, “Advanced Version Control.”

本质上,版本控制系统的目标是双重的:首先,它保留并提供对曾经存储在其中的每个文件的每个版本的访问。这样的系统还为元数据提供了一种方式——即描述存储的数据——附加到单个文件或文件集合。其次,它允许可能分布在不同空间和时间的团队进行协作。

In essence, the aim of a version control system is twofold: First, it retains, and provides access to, every version of every file that has ever been stored in it. Such systems also provide a way for metadata—that is, information that describes the data stored—to be attached to single files or collections of files. Second, it allows teams that may be distributed across space and time to collaborate.

你为什么想做这个?有几个原因,但最终是关于能够回答这些问题:

Why would you want to do this? There are a few reasons, but ultimately it’s about being able to answer these questions:

• 什么构成了您软件的特定版本?您如何重现生产环境中存在的软件二进制文件和配置的特定状态?

• What constitutes a particular version of your software? How can you reproduce a particular state of the software’s binaries and configuration that existed in the production environment?

• 什么时候、由谁、出于什么原因做了什么?这不仅有助于了解何时出现问题,而且还可以讲述您的应用程序的故事。

• What was done when, by whom, and for what reason? Not only is this useful to know when things go wrong, but it also tells the story of your application.

这些是版本控制的基础。大多数项目都使用版本控制。如果您还没有,请阅读接下来的几节,然后将本书放在一边并立即添加。以下几节是我们关于如何最有效地使用版本控制的建议。

These are the fundamentals of version control. Most projects use version control. If yours doesn’t yet, read the next few sections, then put this book aside and add it immediately. The following few sections are our advice on how to make the most effective use of version control.

将所有内容绝对保留在版本控制中

Keep Absolutely Everything in Version Control

我们优先使用术语版本控制而不是源代码控制的一个原因是版本控制不仅仅针对源代码。与软件创建相关的每一个工件都应该在版本控制之下。开发人员应该将它用于源代码,当然,还应该用于测试、数据库脚本、构建和部署脚本、文档、库和应用程序的配置文件、编译器和工具集合等等——这样一个新成员您的团队可以从头开始工作。

One reason that we use the term version control in preference to source control is that version control isn’t just for source code. Every single artifact related to the creation of your software should be under version control. Developers should use it for source code, of course, but also for tests, database scripts, build and deployment scripts, documentation, libraries and configuration files for your application, your compiler and collection of tools, and so on—so that a new member of your team can start working from scratch.

存储重新创建应用程序运行的测试和生产环境所需的所有信息也很重要。这应该包括应用程序软件堆栈的配置信息和构成环境的操作系统、DNS 区域文件、防火墙配置等。至少,您需要重新创建应用程序的二进制文件及其运行环境所需的一切。

It is also important to store all the information required to re-create the testing and production environments that your application runs on. This should include configuration information for your application’s software stack and the operating systems that comprise the environment, DNS zone files, firewall configuration, and so forth. At the bare minimum, you need everything required to re-create your application’s binaries and the environments in which they run.

目标是以受控方式存储项目生命周期中任何时候可能发生变化的所有内容。这使您可以在项目历史的任何时刻恢复整个系统状态的准确快照,从开发环境到生产环境。将开发团队的开发环境的配置文件保存在版本控制中甚至很有帮助,因为它使团队中的每个人都可以轻松使用相同的设置。分析师应该存储需求文档。测试人员应该将他们的测试脚本和过程保存在版本控制中。项目经理应该在这里保存他们的发布计划、进度表和风险日志。简而言之,团队的每个成员都应该在版本控制中存储与项目相关的任何文档或文件。

The objective is to have everything that can possibly change at any point in the life of the project stored in a controlled manner. This allows you to recover an exact snapshot of the state of the entire system, from development environment to production environment, at any point in the project’s history. It is even helpful to keep the configuration files for the development team’s development environments in version control since it makes it easy for everyone on the team to use the same settings. Analysts should store requirements documents. Testers should keep their test scripts and procedures in version control. Project managers should save their release plans, progress charts, and risk logs here. In short, every member of the team should store any document or file related to the project in version control.

我们真的不能足够强调良好的配置管理的重要性。它支持本书中的所有其他内容。如果您没有在版本控制中拥有项目的所有源代码工件,您将无法享受我们在本书中讨论的任何好处。我们讨论的所有用于缩短软件周期时间和提高软件质量的实践,从持续集成和自动化测试到按钮部署,都取决于将与项目相关的所有内容都放在版本控制存储库中。

We really can’t emphasize enough how important good configuration management is. It enables everything else in this book. If you don’t have absolutely every source artifact of your project in version control, you won’t enjoy any of the benefits that we discuss in this book. All of the practices that we discuss to reduce your software’s cycle time and increase its quality, from continuous integration and automated testing to push-button deployments, depend on having everything related to your project in a version control repository.

除了存储源代码和配置信息外,许多项目还在版本控制中存储其应用程序服务器、编译器、虚拟机和工具链其他部分的二进制映像。这非常有用,可以加快新环境的创建速度,更重要的是,可以确保基本配置得到完全定义,因此被认为是好的。只需从版本控制存储库中检查您需要的所有内容,即可确保为开发、测试甚至生产环境提供稳定的平台。然后,您可以将整个环境(包括应用了配置基线的基本操作系统)存储为虚拟映像,以获得更高级别的保证和部署简单性。

In addition to storing source code and configuration information, many projects also store binary images of their application servers, compilers, virtual machines, and other parts of their toolchain in version control. This is fantastically useful, speeding up the creation of new environments and, even more importantly, ensuring that base configurations are completely defined, and so known to be good. Simply checking everything you need out of the version control repository assures a stable platform for development, test, or even production environments. You can then store whole environments, including base operating systems with configuration baselines applied, as virtual images for an even higher level of assurance and deployment simplicity.

这种策略提供了最终的控制和有保证的行为。在如此严格的配置管理下,系统不可能在过程的后期添加错误。这种级别的配置管理确保,只要您保持存储库完好无损,您将始终能够检索软件的工作版本。即使在与该项目相关的编译器、编程语言或其他工具已陷入默默无闻的困境。

This strategy offers the ultimate in control and assured behavior. There is no way for a system under such rigorous configuration management to have errors added at a later stage in the process. This level of configuration management ensures that, provided you keep the repository intact, you will always be able to retrieve a working version of the software. This safeguards you even when the compilers, programming languages, or other tools associated with the project have fallen into the bit-bucket of obscurity.

我们不建议您保留在版本控制中的一件事是应用程序编译的二进制输出。这是出于几个原因。首先,它们很大,而且与编译器不同,它们会迅速扩散(我们为每次编译并通过自动提交测试的签入创建新的二进制文件)。其次,如果您有一个自动构建系统,您应该能够通过重新运行构建脚本从源代码轻松地重新创建它们。请注意:我们不建议将重新编译作为构建过程的正常部分。但是,您的构建系统和源代码的组合是在紧急情况下重新创建应用程序实例所需的全部。最后,存储构建的二进制输出打破了能够为每个应用程序版本识别存储库的单个版本的想法,因为同一版本可能有两个提交,一个用于源代码,另一个用于二进制文件。这可能看起来晦涩难懂,但在创建部署管道时变得极其重要——这是本书的中心主题之一。

One thing that we don’t recommend that you keep in version control is the binary output of your application’s compilation. This is for a few reasons. First, they are big, and unlike compilers, they proliferate rapidly (we create new binaries for every check-in that compiles and passes the automated commit tests). Second, if you have an automated build system, you should be able to re-create them easily from source by rerunning the build script. Please note: We don’t recommend recompilation as a normal part of your build process. However, the combination of your build system and source code is all that should be required to re-create an instance of your application in an emergency. Finally, storing the binary output of the build breaks the idea of being able to identify a single version of your repository for each application version because there may be two commits for the same version, one for source code and another for the binaries. This may seem obscure, but it becomes extremely important when creating deployment pipelines—one of the central topics of this book.

定期检查到主干

Check In Regularly to Trunk

使用版本控制的核心是紧张。一方面,要获得它的许多好处,例如能够返回到您的工件的最近的、已知良好的版本,经常检查是很重要的。

There is a tension at the heart of working with version control. On one hand, to gain access to many of its benefits, such as the ability to step back to a recent, known-good version of your artifacts, it is important to check in frequently.

另一方面,一旦您将更改签入版本控制,它们就会公开,并立即可供团队中的其他所有人使用。此外,如果您按照我们的建议使用持续集成,那么您的更改不仅对团队中的其他开发人员可见;你刚刚诞生​​了一个可能最终进入验收测试甚至生产的构建。

On the other hand, once you check your changes into version control, they become public, instantly available to everybody else on the team. Further, if you are using continuous integration, as we recommend, your changes are not only visible to the other developers on the team; you have just given birth to a build that could potentially end up in acceptance testing or even production.

由于签到是一种发布形式,因此重要的是要确保您的作品(无论是什么)已准备好达到签到所暗示的宣传水平。这尤其适用于开发人员,鉴于他们的工作性质,他们需要对签入的影响保持谨慎。如果开发人员在中间在系统的复杂部分工作时,他们不想在完成之前提交代码;他们希望确信自己的代码处于良好状态并且不会对系统的其他功能产生不利影响。

Since checking in is a form of publication, it is important to be sure that your work, whatever it may be, is ready for the level of publicity that a check-in implies. This applies to developers in particular who, given the nature of their work, need to be cautious about the effects of their check-ins. If a developer is in the middle of working on a complex part of the system, they won’t want to commit their code until it is finished; they want to feel confident that their code is in a good state and won’t adversely affect other functions of the system.

在一些团队中,这可能会导致签到间隔几天甚至几周,这是有问题的。当您定期提交时,版本控制的好处会得到增强。特别是,除非每个人都频繁提交到主线,否则不可能安全地重构应用程序——合并变得太复杂了。如果您频繁提交,您的更改可供其他人查看并与之交互,您会得到一个明确的指示,表明您的更改没有破坏应用程序,并且合并总是很小且易于管理。

In some teams, this can lead to days or even weeks between check-ins, which is problematic. The benefits of version control are enhanced when you commit regularly. In particular, it is impossible to safely refactor an application unless everybody commits frequently to mainline—the merges become too complex. If you commit frequently, your changes are available for other people to see and interact with, you get a clear indication that your changes haven’t broken the application, and merges are always small and manageable.

一些人用来解决这个难题的解决方案是在版本控制系统中为新功能创建一个单独的分支。在某些时候,当更改被认为令人满意时,它们将被合并到主要开发分支中。这有点像两阶段签到;事实上,一些版本控制系统自然地以这种方式工作。

A solution that some people use to resolve this dilemma is to create a separate branch within the version control system for new functionality. At some point, when the changes are deemed satisfactory, they will be merged into the main development branch. This is a bit like a two-stage check-in; in fact, some version control systems work naturally in this way.

然而,我们反对这种做法(除了第 14 章讨论的三个例外)。这是一个有争议的观点,尤其是对于像 ClearCase 这样的工具的用户而言。这种方法存在一些问题。

However, we are opposed to this practice (with three exceptions, discussed in Chapter 14). This is a controversial viewpoint, especially to users of tools like ClearCase. There are a few problems with this approach.

• 它与持续集成是对立的,因为分支的创建延迟了新功能的集成,并且只有在合并分支时才会发现集成问题。

• It is antithetical to continuous integration, since the creation of a branch defers the integration of new functionality, and integration problems are only found when the branch is merged.

• 如果多个开发人员创建分支,问题会呈指数增长,并且合并过程会变得异常复杂。

• If several developers create branches, the problem increases exponentially, and the merge process can become absurdly complex.

• 尽管有一些很棒的自动合并工具,但这些工具并不能解决语义冲突,例如有人在一个分支中重命名一个方法,而其他人在另一个分支中添加对该方法的新调用。

• Although there are some great tools for automated merging, these don’t solve semantic conflicts, such as somebody renaming a method in one branch while somebody else adds a new call to that method in another branch.

• 重构代码库变得非常困难,因为分支往往会涉及许多文件,这使得合并更加困难。

• It becomes very hard to refactor the codebase, since branches tend to touch many files which makes merging even more difficult.

我们将在第 14 章“高级版本控制”中更详细地讨论分支和合并的复杂性。

We will discuss the complexities of branching and merging in more detail in Chapter 14, “Advanced Version Control.”

更好的答案是逐步开发新功能,并定期和频繁地将它们提交到版本控制中的主干。这使软件始终保持运行和集成。这意味着您的软件始终处于测试状态,因为每次您签入时,您的自动化测试都由持续集成(CI)服务器在主干上运行。它减少了重构引起的大型合并冲突的可能性,确保在集成问题时立即捕获它们修复起来很便宜,并且可以产生更高质量的软件。我们将在第 13 章“管理组件和依赖项”中更详细地讨论避免分支的技术。

A much better answer is to develop new features incrementally and to commit them to the trunk in version control on a regular and frequent basis. This keeps the software working and integrated at all times. It means that your software is always tested because your automated tests are run on trunk by the continuous integration (CI) server every time you check in. It reduces the possibility of large merge conflicts caused by refactoring, ensures that integration problems are caught immediately when they are cheap to fix, and results in higher-quality software. We discuss techniques to avoid branching in more detail in Chapter 13, “Managing Components and Dependencies.”

为确保您在签入时不会破坏应用程序,有两种做法很有用。一种是在签入之前运行您的提交测试套件。这是一组快速运行(不到十分钟)但相对全面的测试,可验证您没有引入任何明显的回归。许多持续集成服务器都有一个称为预测试提交的功能,它允许您在签入之前在类似生产的环境中运行这些测试。

To ensure you aren’t going to break the application when you check in, two practices are useful. One is to run your commit test suite before the check-in. This is a quick-running (less than ten minutes) but relatively comprehensive set of tests which validate that you haven’t introduced any obvious regressions. Many continuous integration servers have a feature called pretested commit which allows you to run these tests on a production-like environment before you check in.

二是逐步引入变化。我们建议您的目标是在每个单独的增量更改或重构结束时提交对版本控制系统的更改。如果您正确使用此技术,您应该至少每天检查一次,通常一天检查几次。如果您不习惯这样做,这听起来可能不切实际,但我们向您保证,它会导致更高效的软件交付过程。

The second is to introduce changes incrementally. We recommend that you aim to commit changes to the version control system at the conclusion of each separate incremental change or refactoring. If you use this technique correctly, you should be checking in at the very minimum once a day, and more usually several times a day. This may sound unrealistic if you are not used to doing it, but we assure you, it leads to a far more efficient software delivery process.

使用有意义的提交消息

Use Meaningful Commit Messages

每个版本控制系统都可以为您的提交添加描述。忽略这些消息很容易,许多人养成了这样做的坏习惯。编写描述性提交消息的最重要原因是,当构建中断时,您知道是谁破坏了构建以及原因。但这不是唯一的原因。您的作者曾多次因没有使用足够描述性的提交消息而陷入困境,最常见的是在紧迫的期限内尝试调试复杂问题时。通常的场景是这样运行的:

Every version control system has the facility to add a description to your commit. It is easy to omit these messages, and many people get into the bad habit of doing so. The most important reason to write descriptive commit messages is so that, when the build breaks, you know who broke the build and why. But this is not the only reason. Your authors have been caught out by not using sufficiently descriptive commit messages on several occasions, most often when trying to debug a complex problem under a tight deadline. The usual scenario runs like this:

1. 你发现了一个 bug,该 bug 归结为一行相当晦涩的代码。

1. You find a bug that is down to a rather obscure line of code.

2. 你使用你的版本控制系统来找出谁在什么时候输入了那行代码。

2. You use your version control system to find out who put in that line of code and when.

3.那个人休假或回家过夜,并留下了一条提交信息,上面写着“修复了模糊的错误”。

3. That person is off on holiday or has gone home for the night, and left a commit message that said “fixed obscure bug.”

4. 您更改模糊的代码行以修复错误。

4. You change the obscure line of code to fix the bug.

5. 其他东西坏了。

5. Something else breaks.

6. 您花费数小时试图让应用程序重新运行。

6. You spend hours trying to get the application working again.

在这些情况下,一条提交消息解释了这个人在提交该更改时正在做什么,可以节省您数小时的调试时间。这种情况发生得越多,您就越希望使用好的提交消息。编写最短的提交消息没有奖励。几句中到长的句子概述了您正在做的事情,通常会在以后为您节省很多倍的精力。

In these situations, a commit message explaining what the person was doing when they committed that change can save you hours of debugging. The more this happens, the more you will wish you had used good commit messages. There is no prize for writing the shortest commit message. A couple of medium-to-long sentences with an overview of what you were doing will often save you many times the effort later on.

我们喜欢的一种风格是多段提交消息,其中第一段是摘要,随后的段落添加了更多细节。第一段是在每次提交行显示中显示的内容——可以将其视为报纸标题,为读者提供足够的信息以确定她是否有兴趣继续阅读。

One style we like is a multiparagraph commit message in which the first paragraph is a summary and the following paragraphs add more detail. The first paragraph is what gets shown on line-per-commit displays—think of it as a newspaper headline, giving the reader enough information to figure out if she is interested in reading on.

您还应该在您的项目管理工具中包含一个指向您正在处理的功能或错误的标识符的链接。在我们工作过的许多团队中,系统管理员锁定了他们的版本控制系统,这样不包含此信息的提交就会失败。

You should also include a link to the identifier in your project management tool for the feature or bug you’re working on. On many teams we’ve worked on, the system administrators locked down their version control systems so that commits that do not include this information fail.

管理依赖关系

Managing Dependencies

应用程序中最常见的外部依赖项是它使用的第三方库以及组织内其他团队正在开发的组件或模块之间的关系。库通常以二进制文件的形式部署,应用程序的开发团队从不更改,并且很少更新。组件和模块通常由其他团队积极开发,并且变化非常频繁。

The most common external dependencies within your application are the third-party libraries it uses and the relationships between components or modules under development by other teams within your organization. Libraries are typically deployed in the form of binary files, are never changed by your application’s development team, and are updated very infrequently. Components and modules are typically under active development by other teams and change quite frequently.

我们在第 13 章“管理组件和依赖性”中花了大量时间讨论依赖性。然而,在这里,我们将触及依赖管理的一些关键问题,因为它会影响配置管理。

We spend a great deal of time discussing dependencies in Chapter 13, “Managing Components and Dependencies.” Here, however, we will touch on some of the key issues of dependency management as it impacts configuration management.

管理外部库

Managing External Libraries

外部库通常以二进制形式出现,除非您使用的是解释型语言。即使使用解释性语言,外部库通常也会通过包管理系统(例如 Ruby Gems 或 Perl 模块)在您的系统上全局安装。

External libraries usually come in binary form, unless you’re using an interpreted language. Even with interpreted languages, external libraries are normally installed globally on your system by a package management system such as Ruby Gems or Perl modules.

关于是否对库进行版本控制存在一些争论。例如,Java 的构建工具 Maven 允许您指定应用程序所依赖的 JAR,并从 Internet 上的存储库(或本地缓存,如果有的话)下载它们。

There is some debate as to whether or not to version-control libraries. For example, Maven, a build tool for Java, allows you to specify the JARs your application depends on and downloads them from repositories on the Internet (or a local cache, if you have one).

这并不总是可取的;新的团队成员可能被迫“下载互联网”(或至少下载相当大的部分)才能开始一个项目。但是,它使版本控制检查变得更小。

This is not always desirable; a new team member may be forced to “download the Internet” (or at least decently sized chunks of it) in order to get started on a project. However, it makes the version control check-out much smaller.

我们建议您将外部库的副本保留在本地某处(对于 Maven,您应该为您的组织创建一个存储库,其中包含要使用的已批准版本的库)。如果您必须遵守合规性法规,这是必不可少的,而且它还可以更快地开始项目。这也意味着您始终能够重现您的构建。此外,我们强调您的构建系统应始终指定您使用的外部库的确切版本。如果不这样做,则无法重现您的构建。不能绝对具体也会导致偶尔出现长时间的调试会话,以跟踪由于人员或构建系统使用不同版本的库而导致的奇怪错误。

We recommend that you keep copies of your external libraries somewhere locally (in the case of Maven, you should create a repository for your organization containing approved versions of libraries to use). This is essential if you have to follow compliance regulations, and it also makes getting started on a project faster. It also means you always have the ability to reproduce your build. Furthermore, we emphasize that your build system should always specify the exact version of the external libraries that you use. If you don’t do this, you can’t reproduce your build. Failure to be absolutely specific also leads to an occasional long debugging session tracking down strange errors due to people or build systems using different versions of libraries.

是否将外部库保留在版本控制中涉及一些权衡。它使将软件版本与用于构建它们的库的版本。但是,它使版本控制存储库更大,签出时间更长。

Whether you keep external libraries in version control or not involves some trade-offs. It makes it much easier to correlate versions of your software with the versions of the libraries that were used to build them. However, it makes version control repositories bigger and check-outs longer.

管理组件

Managing Components

将除最小应用程序之外的所有应用程序拆分为组件是一种很好的做法。这样做会限制应用程序的更改范围,减少回归错误。它还鼓励重用并在大型项目上实现更高效的开发过程。

It’s good practice to split all but the smallest applications into components. Doing so limits the scope of changes to your application, reducing regression bugs. It also encourages reuse and enables a much more efficient development process on large projects.

通常,您会从整体构建开始,为整个应用程序一步创建二进制文件或安装程序,通常同时运行单元测试。根据您使用的技术堆栈,整体构建通常是构建中小型应用程序的最有效方式。

Typically, you would start off with a monolithic build creating binaries or an installer for your entire application in one step, usually running unit tests at the same time. Depending on the technology stack you use, a monolithic build is usually the most efficient way to build small and medium-size applications.

但是,如果您的系统增长或您有多个项目依赖的组件,您可以考虑将组件的构建拆分到单独的管道中。如果这样做,重要的是在管道之间具有二进制依赖性而不是源依赖性。重新编译依赖项不仅效率低下;这也意味着您正在创建一个可能与您已经测试过的不同的工件。使用二进制依赖项可能会很难追踪到导致它的源代码更改的破坏,但是一个好的 CI 服务器将帮助您解决这个问题。

However, if your system grows or you have components that several projects depend on, you may consider splitting out your components’ builds into separate pipelines. If you do so, it’s important to have binary dependencies between your pipelines rather than source dependencies. Recompiling dependencies is not only less efficient; it also means you’re creating an artifact that is potentially different from the one that you already tested. Using binary dependencies can make it hard to track back a breakage to the source code change that caused it, but a good CI server will help you with this problem.

虽然现代 CI 服务器在管理依赖项方面做得非常好,但这样做的代价往往是难以在开发人员工作站上为您的应用程序重现整个端到端的构建过程。理想情况下,如果我在我的机器上检查了一些组件,那么对其中一些进行更改并运行一个命令以正确的顺序重建必要的位、创建适当的二进制文件并运行相关测试应该相对简单。不幸的是,这超出了大多数构建系统的能力,至少没有构建工程师的聪明黑客,尽管 Ivy 和 Maven 等工具以及 Gradle 和 Buildr 等脚本技术确实让生活比以前更容易。

While modern CI servers do a pretty good job of managing dependencies, they often do so at the cost of making it harder to reproduce the entire end-to-end build process for your application on a developer workstation. Ideally, if I have a few components checked out on my machine it should be relatively straightforward to make changes in some of them and run a single command that rebuilds the necessary bits in the right order, creates the appropriate binaries, and runs relevant tests. This is, unfortunately, beyond the capability of most build systems, at least without much clever hackery by build engineers, although tools such as Ivy and Maven and scripting technologies such as Gradle and Buildr do make life easier than it used to be.

在第 13 章中有更多关于管理组件和依赖性的内容

There is much more on managing components and dependencies in Chapter 13.

管理软件配置

Managing Software Configuration

配置是构成应用程序的三个关键部分之一,还有它的二进制文件和数据。配置信息可用于在构建时、部署时和运行时更改软件的行为。交付团队需要仔细考虑哪些配置选项应该可用,如何在应用程序的整个生命周期中管理它们,以及如何确保配置在组件、应用程序和技术之间得到一致的管理。我们认为您应该像对待代码一样对待系统配置:使其受到适当的管理和测试。

Configuration is one of the three key parts that comprise an application, along with its binaries and its data. Configuration information can be used to change the behavior of software at build time, deploy time, and run time. Delivery teams need to consider carefully what configuration options should be available, how to manage them throughout the application’s life, and how to ensure that configuration is managed consistently across components, applications, and technologies. We believe that you should treat the configuration of your system in the same way you treat your code: Make it subject to proper management and testing.

配置和灵活性

Configuration and Flexibility

如果被问到,每个人都想要灵活的软件。你为什么不呢?但灵活性通常是有代价的。

If asked, everyone wants flexible software. Why would you not? But flexibility usually comes at a cost.

显然存在一个连续统一体:一方面,有单一用途的软件可以很好地完成一项工作,但几乎没有或根本没有能力修改其行为。另一方面,您可以使用一种编程语言来编写游戏、应用程序服务器或库存控制系统——这就是灵活性!但是,大多数应用程序都没有极端。相反,它们是为特定目的而设计的,但在该目的范围内,它们通常会有一些可以修改其行为的方法。

Clearly there is a continuum: At one end, there is single-purpose software that does one job well but has little or no ability to have its behavior modified. At the other end of the spectrum is a programming language that you can use to write a game, an application server, or a stock control system—that is flexibility! Most applications, though, are at neither extreme. Instead, they are designed for a specific purpose, but within the bounds of that purpose they will usually have some ways in which their behavior can be modified.

实现灵活性的愿望可能会导致“最终可配置性”的常见反模式,这种模式经常被表述为软件项目的要求。它充其量是无益的,最坏的情况是,这一要求可能会扼杀一个项目。

The desire to achieve flexibility may lead to the common antipattern of “ultimate configurability” which is, all too frequently, stated as a requirement for software projects. It is at best unhelpful, and at worst, this one requirement can kill a project.

任何时候,您都可以更改正在编写的应用程序的行为。您编写更改所用的语言可能或多或少受到限制,但它仍然是编程。根据定义,您打算为用户提供的可配置性越强,您对系统配置施加的约束就越少,因此编程环境需要变得越复杂。

Any time, you change the behavior of an application you are programming. The language in which you are programming the changes may be more or less constrained, but it is programming nevertheless. The more configurability you intend to offer users, by definition, the fewer constraints you can afford to place on the configuration of the system, and so the more sophisticated the programming environment needs to become.

根据我们的经验,配置信息在某种程度上比源代码更改风险更小,这是一个长期存在的神话。我们打赌,如果可以访问这两者,我们至少可以通过更改配置和更改源代码一样轻松地停止您的系统。如果我们更改源代码,我们可以通过多种方式保护自己免受伤害;编译器会排除无意义的错误,而自动化测试应该会捕获大多数其他错误。另一方面,大多数配置信息都是自由形式且未经测试的。在大多数系统中,没有什么可以阻止我们将 URI 从“ http://www.asciimation.co.nz/”到“这不是有效的 URI”。大多数系统直到运行时才会捕捉到这样的变化——此时,您的用户不会享受星战的 ASCII 版本,而是会看到一个讨厌的异常报告,因为 URI 类无法解析“这不是一个有效的 URI。”

In our experience, it is an enduring myth that configuration information is somehow less risky to change than source code. Our bet is that, given access to both, we can stop your system at least as easily by changing the configuration as by changing the source code. If we change the source code, there are a variety of ways in which we are protected from ourselves; the compiler will rule out nonsense, and automated tests should catch most other errors. On the other hand, most configuration information is free-form and untested. In most systems there is nothing to prevent us from changing a URI from “http://www.asciimation.co.nz/” to “this is not a valid URI.” Most systems won’t catch a change like this until run time—at which point, instead of enjoying the ASCII version of Star Wars, your users are presented with a nasty exception report because the URI class can’t parse “this is not a valid URI.”

在通往高度可配置软件的道路上有许多重大陷阱,但最糟糕的可能是以下这些。

There are many significant pitfalls on the road to highly configurable software, but perhaps the worst are the following.

• 它经常导致分析瘫痪,在这种情况下,问题看起来如此之大且如此棘手,以至于团队将所有时间都花在了思考如何解决问题上,却没有时间实际解决任何问题。

• It frequently leads to analysis paralysis, in which the problem seems so big and so intractable that the team spends all of their time thinking about how to solve it and none of their time actually solving anything.

• 系统配置变得如此复杂,以至于失去了其灵活性的许多好处,以至于其配置所涉及的工作量与定制开发的成本相当。

• The system becomes so complex to configure that many of the benefits of its flexibility are lost, to the extent where the effort involved in its configuration is comparable to the cost of custom development.

不要误解我们:配置并非天生邪恶。但它需要谨慎和一致地管理。现代计算机语言已经发展出各种特性和技术来帮助它们减少错误。在大多数情况下,配置信息不存在这些保护措施,而且通常甚至没有任何测试来验证您的软件是否已在测试和生产环境中正确配置。部署冒烟测试,如第 117 页的“冒烟测试您的部署”部分所述,是缓解此问题的一种方法,应始终使用。

Don’t misunderstand us: Configuration is not inherently evil. But it needs to be managed carefully and consistently. Modern computer languages have evolved all sorts of characteristics and techniques to help them reduce errors. In most cases, these protections do not exist for configuration information, and more often than not there are not even any tests in place to verify that your software has been configured correctly in testing and production environments. Deployment smoke tests, as described in the “Smoke-Test Your Deployments” section on page 117, are one way to mitigate this problem and should always be used.

配置类型

Types of Configuration

配置信息可以在构建、部署、测试和发布过程中的多个点注入到您的应用程序中,并且通常在多个点包含配置信息。

Configuration information can be injected into your application at several points in your build, deploy, test, and release process, and it’s usual for it to be included at more than one point.

• 您的构建脚本可以在构建时提取配置并将其合并到您的二进制文件中

• Your build scripts can pull configuration in and incorporate it into your binaries at build time.

• 您的打包软件可以在打包时注入配置,例如在创建程序集、耳朵或宝石时。

• Your packaging software can inject configuration at packaging time, such as when creating assemblies, ears, or gems.

•作为安装过程的一部分,您的部署脚本或安装程序可以获取必要的信息或询问用户并在部署时将其传递给您的应用程序。

• Your deployment scripts or installers can fetch the necessary information or ask the user for it and pass it to your application at deployment time as part of the installation process.

• 您的应用程序本身可以在启动时或运行时获取配置。

• Your application itself can fetch configuration at startup time or run time.

通常,我们认为在构建或打包时注入配置信息是不好的做法。这是根据您应该能够将相同的二进制文件部署到每个环境,这样您就可以确保您发布的东西与您测试的东西相同。这样做的必然结果是,部署之间的任何更改都需要作为配置进行捕获,而不是在编译或打包应用程序时嵌入。

Generally, we consider it bad practice to inject configuration information at build or packaging time. This follows from the principle that you should be able to deploy the same binaries to every environment so you can ensure that the thing that you release is the same thing that you tested. The corollary of this is that anything that changes between deployments needs to be captured as configuration, and not baked in when the application is compiled or packaged.

能够在部署时配置您的应用程序通常很重要,这样您就可以告诉它它所依赖的服务(例如数据库、消息传递服务器或外部系统)属于哪里。例如,如果应用程序的运行时配置存储在数据库中,您可能希望在部署时将数据库的连接参数传递给应用程序,以便它可以在启动时检索它。

It is usually important to be able to configure your application at deployment time so that you can tell it where the services it depends upon (such as database, messaging servers, or external systems) belong. For example, if the runtime configuration of your application is stored in a database, you may want to pass the database’s connection parameters to the application at deployment time so it can retrieve it when it starts up.

如果您控制您的生产环境,您通常可以安排您的部署脚本来获取此配置并将其提供给您的应用程序。在打包软件的情况下,默认配置通常是包的一部分,但为了测试目的,需要有某种方法在部署时覆盖它。

If you control your production environment, you can usually arrange for your deployment scripts to fetch this configuration and supply it to your application. In the case of packaged software, the default configuration is normally part of the package, but there needs to be some way to override it at deployment time for testing purposes.

最后,您可能需要在启动时或运行时配置您的应用程序。启动时配置可以以环境变量的形式提供,也可以作为用于启动系统的命令的参数提供。或者,您可以使用与运行时配置相同的机制:注册表设置、数据库、配置文件或外部配置服务(例如,通过 SOAP 或 REST 式接口访问)。

Finally, you may need to configure your application at startup time or at run time. Startup-time configuration can be supplied either in the form of environment variables or as arguments to the command used to start the system. Alternatively, you can use the same mechanisms that you use for runtime configuration: registry settings, a database, configuration files, or an external configuration service (accessed via SOAP or a REST-style interface, for example).

无论您选择何种机制,我们都强烈建议您尽可能尝试通过相同的机制为组织中的所有应用程序和环境提供所有配置信息。这并不总是可能的,但如果可能的话,这意味着有一个单一的配置源可以更改、管理、版本控制和覆盖(如果需要)。在不遵循这种做法的组织中,我们看到人们经常花费数小时来追踪其环境之一中某些特定设置的来源。

Whatever mechanism you choose, we strongly recommend that, as far as practically possible, you should try and supply all configuration information for all the applications and environments in your organization through the same mechanism. This isn’t always possible, but when it is, it means that there is a single source of configuration to change, manage, version-control, and override (if necessary). In organizations where this practice isn’t followed, we have seen people regularly spend hours tracking down the source of some particular setting in one of their environments.

管理应用程序配置

Managing Application Configuration

管理应用程序配置时需要考虑三个问题:

There are three questions to consider when managing your application’s configuration:

1. 你如何表示你的配置信息?

1. How do you represent your configuration information?

2. 你的部署脚本如何访问它?

2. How do your deployment scripts access it?

3. 它在环境、应用程序和应用程序版本之间有何不同?

3. How does it vary between environments, applications, and versions of applications?

配置信息通常建模为一组名称-值字符串。1个

Configuration information is often modeled as a set of name-value strings.1

有时在配置系统中使用类型并按层次组织它很有用。包含按标题组织的名称-值字符串的 Windows 属性文件、Ruby 世界中流行的 YAML 文件和 Java 属性文件是相对简单的格式,在大多数情况下提供了足够的灵活性。复杂性的有用限制可能是将配置存储为 XML 文件。

Sometimes it is useful to use types in your configuration system and to organize it hierarchically. Windows properties files that contain name-value strings organized by headings, YAML files popular in the Ruby world, and Java properties files are relatively simple formats that provide enough flexibility in most cases. Probably the useful limit of complexity is to store configuration as an XML file.

对于存储应用程序配置的位置,有几个显而易见的选择:数据库、版本控制系统或者目录或注册表。版本控制可能是最简单的——您只需检查您的配置文件,您就可以免费获得您的配置随时间推移的历史记录。值得将应用程序的可用配置选项列表保存在与其源代码相同的存储库中。

There are a few obvious choices for where to store your application configuration: a database, a version control system, or a directory or registry. Version control is probably the easiest—you can just check in your configuration file, and you get the history of your configuration over time for free. It is worth keeping a list of the available configuration options for your application in the same repository as its source code.


图片

请注意,您存储配置的位置与您的应用程序访问它的机制不同。您的应用程序可以通过其本地文件系统上的文件访问其配置,或者通过更奇特的机制(例如 Web 或目录服务)或通过数据库;下一节将详细介绍这一点。

Note that the place where you store configuration is not the same as the mechanism by which your application accesses it. Your application can access its configuration via a file on its local filesystem, or through more exotic mechanisms such as a web or directory service, or via a database; more on this in the next section.


将特定于每个应用程序的测试和生产环境的实际配置信息保存在与源代码分开的存储库中通常很重要。此信息通常以与其他版本控制的工件不同的速率变化。但是,如果您采用这种方式,则必须注意跟踪哪些版本的配置信息与哪些版本的应用程序相匹配。这种分离与安全相关的配置元素特别相关,例如密码和数字证书,访问应受到限制。

It is often important to keep the actual configuration information specific to each of your application’s testing and production environments in a repository separate from your source code. This information generally changes at a different rate to other version-controlled artifacts. However, if you take this route, you will have to be careful to track which versions of configuration information match with which versions of the application. This separation is particularly relevant for security-related configuration elements, such as passwords and digital certificates, to which access should be restricted.

数据库、目录和注册表是存储配置的便利位置,因为它们可以远程访问。但是,为了审计和回滚的目的,请确保保留配置更改的历史记录。要么拥有一个自动处理此问题的系统,要么将版本控制视为您的配置参考系统,并拥有一个脚本,可根据需要将适当的版本加载到您的数据库或目录中。

Databases, directories, and registries are convenient places to store configuration since they can be accessed remotely. However, make sure to keep the history of changes to configuration for the purposes of audit and rollback. Either have a system that automatically takes care of this, or treat version control as your system of reference for configuration and have a script that loads the appropriate version into your database or directory on demand.

访问配置

Accessing Configuration

管理配置最有效的方法是拥有一个中央服务,每个应用程序都可以通过它获得所需的配置。对于打包软件和内部企业应用程序以及托管在 Internet 上的服务软件都是如此。这些场景之间的主要区别在于您何时注入配置信息——在打包软件的打包时,或者在部署时或运行时。

The most efficient way to manage configuration is to have a central service through which every application can get the configuration it needs. This is as true for packaged software as it is for internal corporate applications and software as a service hosted on the Internet. The main difference between these scenarios is in when you inject the configuration information—at packaging time for packaged software, or at deploy time or run time otherwise.

应用程序访问其配置的最简单方法可能是通过文件系统。这具有跨平台和支持所有语言的优势——尽管它可能不适合沙盒运行时,例如 applet。如果您需要在集群上运行您的应用程序,还存在保持文件系统配置同步的问题。

Probably the easiest way for an application to access its configuration is via the filesystem. This has the advantage of being cross-platform and supported in every language—although it may not be suitable for sand-boxed runtimes such as applets. There is also the problem of keeping configuration on filesystems in sync if, for example, you need to run your application on a cluster.

另一种选择是从 RDBMS、LDAP 或 Web 服务等集中存储库中获取配置。一个名为 ESCAPE [apvrEr] 的开源工具可以通过 RESTful 接口轻松管理和访问配置信息。应用程序可以执行 HTTP GET,在 URI 中包含应用程序和环境名称以获取其配置。在部署时或运行时配置应用程序时,此机制最有意义。您将环境名称传递给您的部署脚本(通过属性、命令行开关或环境变量),然后您脚本从配置服务中获取适当的配置并将其提供给应用程序,可能作为文件系统上的一个文件。

Another alternative is to fetch configuration from a centralized repository such as a RDBMS, LDAP, or a web service. An open source tool called ESCAPE [apvrEr] makes it easy to manage and access configuration information via a RESTful interface. Applications can perform an HTTP GET which includes the application and environment name in the URI to fetch their configuration. This mechanism makes most sense when configuring your application at deployment time or run time. You pass the environment name to your deployment scripts (via a property, command-line switch, or environment variable), and then your scripts fetch the appropriate configuration from the configuration service and make it available to the application, perhaps as a file on the filesystem.

无论配置信息存储的性质如何,我们都建议您使用提供

Whatever the nature of the configuration information store, we recommend that you insulate the detail of the technology from your application with a simple façade class providing a

getThisProperty()

getThatProperty()

getThisProperty()

getThatProperty()

界面风格,因此您可以在测试中伪造它并在需要时更改存储机制。

style of interface, so you can fake it in tests and change the storage mechanism when you need to.

建模配置

Modeling Configuration

每个配置设置都可以建模为一个元组,因此应用程序的配置由一组元组组成。但是,可用的元组集及其值通常取决于三件事:

Each configuration setting can be modeled as a tuple, so the configuration for an application consists of a set of tuples. However, the set of the tuples available and their values typically depend on three things:

• 应用程序

• The application

• 应用程序的版本

• The version of the application

• 它运行的环境(例如,开发、UAT、性能、暂存或生产)

• The environment it runs in (for example, development, UAT, performance, staging, or production)

因此,例如,报告应用程序的 1.0 版将有一组不同于 2.2 版或投资组合管理应用程序的 1.0 版的元组。反过来,这些元组的值将根据它们部署到的环境而变化。例如,UAT 中的应用程序使用的数据库服务器通常与生产中使用的不同,甚至可能因开发人员机器而异。这同样适用于打包软件或外部集成点——您的应用程序使用的更新服务在运行集成测试时与从客户桌面访问时会有所不同。

So, for example, version 1.0 of your reporting application will have a set of tuples different from version 2.2, or from version 1.0 of your portfolio management application. The values of those tuples will, in turn, vary depending on the environment they are deployed into. For example, the database server used by the application in UAT will typically be different from that used in production and may even vary between developer machines. The same applies to packaged software or external integration points—an update service used by your application will be different when running integration tests from when it is accessed from a customer’s desktop.

无论您使用什么来表示和提供配置信息——源代码管理中的 XML 文件或 RESTful Web 服务——都应该能够处理这些不同的维度。以下是在对配置信息进行建模时要考虑的一些用例。

Whatever you use to represent and serve configuration information—XML files in source control or a RESTful web service—should be able to handle these various dimensions. Here are some use cases to consider when modeling configuration information.

• 添加新环境(可能是新的开发人员工作站,或者容量测试环境)。在这种情况下,您需要能够为部署到这个新环境中的应用程序指定一组新的值。

• Adding a new environment (a new developer workstation perhaps, or a capacity testing environment). In this case you’d need to be able to specify a new set of values for applications deployed into this new environment.

• 创建应用程序的新版本。通常,这会引入新的配置设置并删除一些旧的。您应该确保当您将这个新版本部署到生产环境时,它可以获得新设置,但如果您必须回滚到旧版本,它将使用旧版本。

• Creating a new version of the application. Often, this will introduce new configuration settings and get rid of some old ones. You should ensure that when you deploy this new version to production, it can get its new settings, but if you have to roll back to an older version it will use the old ones.

• 将应用程序的新版本从一种环境提升到另一种环境。您应该确保任何新设置在新环境中可用,但要为这个新环境设置适当的值。

• Promoting a new version of your application from one environment to another. You should ensure that any new settings are available in the new environment, but that the appropriate values are set for this new environment.

• 重新定位数据库服务器。您应该能够非常简单地更新引用该数据库的每个配置设置,使其指向新数据库。

• Relocating a database server. You should be able to update, very simply, every configuration setting that references this database to make it point to the new one.

• 使用虚拟化管理环境。您应该能够使用您的虚拟化管理工具创建一个特定环境的新实例,该实例已正确配置所有 VM。您可能希望将此信息作为部署到该环境中的特定应用程序版本的配置设置的一部分。

• Managing environments using virtualization. You should be able to use your virtualization management tool to create a new instance of a particular environment that has all the VMs configured correctly. You may want to include this information as part of the configuration settings for the particular version of the application deployed into that environment.

跨环境管理配置的一种方法是将预期的生产配置设置为默认配置,并在其他环境中适当地覆盖此默认配置(确保您有适当的防火墙,以便生产系统不会被错误攻击)。这意味着任何特定于环境的定制都减少到只有那些必须更改的配置属性,软件才能在该特定环境中工作。这简化了需要在哪里配置什么的图片。但是,这也取决于您的应用程序的生产配置是否具有特权——一些组织希望生产配置与其他环境的配置保存在不同的存储库中。

One approach to managing configuration across environments is to make the expected production configuration the default and to override this default in other environments as appropriate (ensure you have firewalls in place so that production systems don’t get hit by mistake). This means that any environment-specific tailoring is reduced to only those configuration properties that must be changed for the software to work in that particular environment. This simplifies the picture of what needs to be configured where. However, it also depends on whether or not your application’s production configuration is privileged—some organizations expect the production configuration to be kept in a separate repository from that of other environments.

测试系统配置

Testing System Configuration

就像您的应用程序和构建脚本需要测试一样,您的配置设置也是如此。测试配置有两个部分。

In the same way that your application and build scripts need testing, so do your configuration settings. There are two parts to testing configuration.

第一阶段是确保在您的配置设置中对外部服务的引用是正确的。作为部署脚本的一部分,您应该确保您配置使用的消息传递总线实际启动并在配置的地址运行,并且您的应用程序期望在功能测试环境中使用的模拟订单履行服务正在运行。至少,您可以 ping 所有外部服务。如果您的应用程序所依赖的任何东西不可用,您的部署或安装脚本应该会失败——这可以作为对您的配置设置的一个很好的冒烟测试。

The first stage is to ensure that references to external services in your configuration settings are good. You should, as part of your deployment script, ensure that the messaging bus you are configured to use is actually up and running at the address configured, and that the mock order fulfillment service your application expects to use in the functional testing environment is working. At the very least, you could ping all external services. Your deployment or installation script should fail if anything your application depends on is not available—this acts as a great smoke test for your configuration settings.

第二阶段是在安装应用程序后实际运行一些冒烟测试,以确保它按预期运行。这应该只涉及一些测试,这些测试会根据正确的配置设置来行使功能。理想情况下,如果结果不符合预期,这些测试应该停止应用程序并使安装或部署过程失败。

The second stage is to actually run some smoke tests once your application is installed to make sure it is operating as expected. This should involve just a few tests exercising functionality that depends on the configuration settings being correct. Ideally, these tests should stop the application and fail the installation or deployment process if the results are not as expected.

跨应用程序管理配置

Managing Configuration across Applications

在许多应用程序必须一起管理的大中型组织中,管理配置的问题尤为复杂。通常在这样的组织中,遗留应用程序存在深奥的配置选项,人们对此知之甚少。最重要的任务之一是保留每个应用程序具有的所有配置选项的目录、它们的存储位置、它们的生命周期是什么以及如何更改它们。

The problem of managing configuration is particularly complex in medium and large organizations where many applications have to be managed together. Usually in such organizations, legacy applications exist with esoteric configuration options that are poorly understood. One of the most important tasks is to keep a catalogue of all the configuration options that each of your applications has, where they are stored, what their lifecycle is, and how they can be changed.

如果可能,此类信息应作为构建过程的一部分从每个应用程序的代码中自动生成。但如果这不可能,则应将其收集在 wiki 或其他文档管理系统中。

If possible, such information should be generated automatically from each application’s code as part of the build process. But where this is not possible, it should be collected in a wiki or other document management system.

在管理不完全由用户安装的应用程序时,了解每个正在运行的应用程序的当前配置是什么很重要。目标是能够通过运营团队的生产监控系统查看每个应用程序的配置,该系统还应显示每个应用程序在每个环境中部署的版本。Nagios、OpenNMS、HP OpenView等工具都提供了记录这些信息的服务。或者,如果您以自动化方式管理您的构建和部署过程,您的配置信息应该始终通过这个过程应用,因此存储在版本控制或像 Escape 这样的工具中。

When managing applications that are not entirely user-installed, it is important to know what the current configuration of each running application is. The goal is to be able to see each application’s configuration through your operation team’s production monitoring system, which should also display which version of each application is deployed in each environment. Tools such as Nagios, OpenNMS, and HP OpenView all provide services to record such information. Alternatively, if you manage your building and deployment process in an automated fashion, your configuration information should always be applied through this process, and hence be stored in version control or a tool like Escape.

当您的应用程序相互依赖并且必须协调部署时,实时访问此信息尤为重要。一个应用程序因错误设置了一些配置选项而导致整套服务中断,已经浪费了无数个小时。此类问题极难诊断。

It is especially important to have access to this information on a real-time basis when your applications depend on each other and deployments must be orchestrated. Countless hours have been lost by one application having a few configuration options set wrongly and thereby bringing down an entire set of services. Such problems are extremely hard to diagnose.

每个应用程序的配置管理都应该作为项目启动的一部分进行规划。考虑生态系统中的其他应用程序如何管理其配置并尽可能使用相同的方法。很多时候,关于如何管理配置的决定是临时做出的,因此每个应用程序将其配置打包在不同的地方并使用不同的机制来访问它。这使得确定环境的配置变得不必要地困难。

Configuration management of every application should be planned as part of project inception. Consider how other applications in your ecosystem manage their configuration and use the same method, if possible. Too often, decisions on how to manage configuration are done on an ad-hoc basis, and as a result every application packages its configuration in a different place and uses a different mechanism for accessing it. This makes it unnecessarily hard to determine the configuration of your environments.

管理应用程序配置的原则

Principles of Managing Application Configuration

像对待代码一样对待应用程序的配置。妥善管理它,并测试它。以下是创建应用程序配置系统时要考虑的原则列表:

Treat your application’s configuration the same way you treat your code. Manage it properly, and test it. Here is a list of principles to consider when creating an application configuration system:

• 考虑在您的应用程序生命周期中的什么地方注入特定的配置是有意义的——在您所在的组装点在部署或安装时、启动时或运行时打包您的候选版本。与运营和支持团队交谈,了解他们的需求。

• Consider where in your application’s lifecycle it makes sense to inject a particular piece of configuration—at the point of assembly where you are packaging your release candidate, at deployment or installation time, at startup time, or at run time. Speak to the operations and support team to work out what their needs are.

• 将应用程序的可用配置选项保存在与其源代码相同的存储库中,但将值保存在其他地方。配置设置的生命周期与代码的生命周期完全不同,而密码和其他敏感信息根本不应签入版本控制。

• Keep the available configuration options for your application in the same repository as its source code, but keep the values somewhere else. Configuration settings have a lifecycle completely different from that of code, while passwords and other sensitive information should not be checked in to version control at all.

• 配置应始终由自动化流程使用从您的配置存储库中获取的值来执行,以便您始终可以识别每个环境中每个应用程序的配置。

• Configuration should always be performed by automated processes using values taken from your configuration repository, so that you can always identify the configuration of every application in every environment.

• 您的配置系统应该能够根据应用程序、版本和部署环境为您的应用程序(包括其打包、安装和部署脚本)提供不同的值。任何人都应该很容易看到哪些配置选项可用于某个特定版本的应用程序,跨所有环境部署。

• Your configuration system should be able to provide different values to your application (including its packaging, installation, and deployment scripts) based on the application, its version, and the environment it is being deployed into. It should be easy for anyone to see what configuration options are available for a particular version of an application across all environments it will be deployed into.

• 为您的配置选项使用清晰的命名约定。避免晦涩或神秘的名称。试着想象有人在没有手册的情况下阅读配置文件——应该可以理解配置属性是什么。

• Use clear naming conventions for your configuration options. Avoid obscure or cryptic names. Try to imagine someone reading the configuration file without a manual—it should be possible to understand what the configuration properties are.

• 确保您的配置信息是模块化和封装的,这样一个地方的更改不会对其他明显不相关的配置片段产生连锁反应。

• Ensure that your configuration information is modular and encapsulated so that changes in one place don’t have knock-on effects for other, apparently unrelated, pieces of configuration.

• 使用DRY(不要重复自己)原则。定义配置的元素,以便每个概念在配置信息集中只有一个表示。

• Use the DRY (don’t repeat yourself) principle. Define the elements of your configuration so that each concept has only one representation in the set of configuration information.

• 极简主义:使配置信息尽可能简单和集中。避免创建配置选项,除非有要求或这样做有意义。

• Be minimalist: Keep the configuration information as simple and as focused as possible. Avoid creating configuration options except where there is a requirement or where it makes sense to do so.

• 避免过度设计配置系统。尽可能保持简单。

• Avoid overengineering the configuration system. Keep it as simple as you can.

• 确保您拥有在部署或安装时运行的配置测试。检查您的应用程序所依赖的服务是否可用,并使用冒烟测试来断言任何取决于您的配置设置的功能是否正常工作。

• Ensure that you have tests for your configuration that are run at deployment or installation time. Check that the services your application depends upon are available, and use smoke tests to assert that any functionality depending on your configuration settings works as it should.

管理您的环境

Managing Your Environments

没有应用程序是一座孤岛。每个应用程序都依赖于硬件、软件、基础设施和外部系统才能工作。在本书中,我们将此称为应用程序的环境。我们在第 11 章“管理基础设施和环境”中详细讨论了环境管理主题,但该主题值得在配置管理的上下文中进行一些讨论,因此我们将在此处介绍。

No application is an island. Every application depends on hardware, software, infrastructure, and external systems in order to work. We refer to this, throughout this book, as your application’s environment. We deal, at some length, with the topic of environment management in Chapter 11, “Managing Infrastructure and Environments,” but the topic deserves some discussion in the context of configuration management, so we will introduce it here.

管理应用程序运行环境时要牢记的原则是,该环境的配置与应用程序的配置一样重要。例如,如果您的应用程序依赖消息传递总线,则需要正确配置总线,否则应用程序将无法运行。您的操作系统配置也很重要。例如,您的应用程序可能依赖于大量可用的文件描述符。如果操作系统默认文件描述符数量的下限,您的应用程序将无法运行。

The principle to bear in mind when managing the environment that your application runs in is that the configuration of that environment is as important as the configuration of the application. If, for example, your application depends on a messaging bus, the bus needs to be configured correctly or the application will not work. Your operating system’s configuration is also important. For example, you may have an application that relies on a large number of file descriptors being available. If the operating system defaults to a lower limit for the number of file descriptors, your application won’t work.

管理配置信息最糟糕的方法是临时处理它。这意味着手动安装必要的软件并编辑相关的配置文件。这是我们遇到的最常见的策略。虽然看似简单,但这种策略有几个常见问题,除了最微不足道的系统外,所有系统都会出现这些问题。最明显的陷阱是,如果由于某种原因新配置不起作用,则很难确定地返回到已知的良好状态,因为没有先前配置的记录。问题可以总结如下:

The worst approach to managing configuration information is to deal with it on an ad-hoc basis. This means installing the requisite pieces of software by hand and editing the relevant configuration files. This is the most common strategy that we encounter. Although seemingly simple, this strategy has several common problems that arise in all but the most trivial of systems. The most obvious pitfall is that if, for any reason, the new configuration doesn’t work, it’s difficult to return to a known good state with any certainty since there is no record of the previous configuration. The problem can be summed up as follows:

• 配置信息的集合非常大。

• The collection of configuration information is very large.

• 一个小的更改可能会破坏整个应用程序或严重降低其性能。

• One small change can break the whole application or severely degrade its performance.

• 一旦出现问题,发现问题并修复它需要的时间不确定,而且需要高级人员。

• Once it is broken, finding the problem and fixing it takes an indeterminate amount of time and requires senior personnel.

• 为测试目的精确复制手动配置的环境是极其困难的。

• It is extremely difficult to precisely reproduce manually configured environments for testing purposes.

• 如果不同节点的配置和行为不同,则很难维护这样的环境。

• It is difficult to maintain such environments without the configuration, and hence behavior, of different nodes drifting apart.

The Visible Ops Handbook中,作者将手动配置的环境称为“艺术品”。为了降低管理环境的成本和风险,必须将我们的环境转变为可重复创建且需要可预测时间量的大规模生产对象。我们有参与了太多的项目,其中糟糕的配置管理意味着巨大的开支——支付团队人员单独处理系统的这一方面的费用。它还会持续拖累开发过程的生产力,使测试环境、开发环境和生产环境的部署比他们需要的更加复杂和昂贵。

In The Visible Ops Handbook the authors refer to manually configured environments as “works of art.” In order to reduce the cost and risk of managing environments, it is essential to turn our environments into mass-produced objects whose creation is repeatable and takes a predictable amount of time. We have been involved in too many projects where poor configuration management has meant significant expense—paying for teams of people to work on this aspect of the system alone. It also acts as a continual drag on the productivity of the development process, making deployments to test environments, development environments, and into production much more complex and costly than they need to be.

管理环境的关键是使它们的创建成为一个完全自动化的过程。创建新环境总是比修复旧环境成本更低。出于多种原因,能够重现您的环境至关重要。

The key to managing environments is to make their creation a fully automated process. It should always be cheaper to create a new environment than to repair an old one. Being able to reproduce your environments is essential for several reasons.

• 它消除了随机基础设施的问题,这些基础设施的配置只有离开组织的人才能理解并且无法接触到。当这些东西停止工作时,您通常可以假设停机时间很长。这是一个很大且不必要的风险。

• It removes the problem of having random pieces of infrastructure around whose configuration is only understood by somebody who has left the organization and cannot be reached. When such things stop working, you can usually assume a significant downtime. This is a large and unnecessary risk.

• 修复其中一个环境可能需要花费数小时。能够在可预测的时间内重建它以恢复到已知的良好状态总是更好。

• Fixing one of your environments can take many hours. It is always better to be able to rebuild it in a predictable amount of time so as to get back to a known good state.

• 能够为测试目的创建生产环境的副本是必不可少的。在软件配置方面,测试环境应该是生产环境的精确复制,这样可以及早发现配置问题。

• It is essential to be able to create copies of production environments for testing purposes. In terms of software configuration, testing environments should be exact replicas of the production ones, so configuration problems can be found early.

您应该关注的环境配置信息有:

The kinds of environment configuration information you should be concerned about are:

• 您环境中的各种操作系统,包括它们的版本、补丁级别和配置设置

• The various operating systems in your environment, including their versions, patch levels, and configuration settings

• 需要在每个环境中安装以支持您的应用程序的附加软件包,包括它们的版本和配置

• The additional software packages that need to be installed on each environment to support your application, including their versions and configuration

• 应用程序运行所需的网络拓扑

• The networking topology required for your application to work

• 您的应用程序所依赖的任何外部服务,包括它们的版本和配置

• Any external services that your application depends upon, including their versions and configuration

• 其中存在的任何数据或其他状态(例如,生产数据库)

• Any data or other state that is present in them (for example, production databases)

正如我们所发现的,有两个原则构成了有效配置管理策略的基础: 保持二进制文件独立从配置信息,并将所有配置信息保存在一个地方。将这些基础知识应用到系统的每个部分将为创建新环境、升级系统部分和推出新配置而不使系统不可用成为一个简单的自动化过程铺平道路。

There are two principles that, as we have found, form the basis of an effective configuration management strategy: Keep binary files independent from configuration information, and keep all configuration information in one place. Applying these fundamentals to every part of your system will pave the way to the point where creating new environments, upgrading parts of your system, and rolling out new configurations without making your system unavailable becomes a simple, automated process.

所有这些事情都需要考虑。尽管将您的操作系统签入版本控制显然是不合理的,但对其配置进行版本控制肯定不是不合理的。远程安装系统和环境管理工具(如 Puppet 和 CfEngine)的组合使操作系统的集中管理和配置变得简单明了。第 11 章“管理基础设施和环境”中详细介绍了该主题。

All of these things need to be considered. Although it’s obviously unreasonable to check your operating system into version control, it’s certainly not unreasonable to version-control its configuration. A combination of remote installation systems and environment management tools such as Puppet and CfEngine make centralized management and configuration of operating systems straightforward. This topic is covered in detail in Chapter 11, “Managing Infrastructure and Environments.”

对于大多数应用程序来说,将这一原则应用到它们所依赖的第三方软件栈上就显得尤为重要。好的软件具有可以从命令行运行而无需任何用户干预的安装程序。它具有可以在版本控制中管理的配置,不需要人工干预。如果您的第三方软件依赖项不符合这些标准,您应该寻找替代方案——这些第三方软件选择标准非常重要,它们应该成为每个软件评估活动的核心。在评估第三方产品和服务时,首先要问以下问题:

For most applications, it is even more important to apply this principle to the third-party software stack that they depend on. Good software has installers that can be run from the command line without any user intervention. It has configuration that can be managed in version control and does not require manual intervention. If your third-party software dependencies don’t meet these criteria, you should find alternatives—these criteria for third-party software selection are of such importance that they should be at the core of every software evaluation exercise. When evaluating third-party products and services, start by asking the following questions:

• 我们可以部署它吗?

• Can we deploy it?

• 我们能否有效地对其配置进行版本控制?

• Can we version its configuration effectively?

• 它将如何适应我们的自动化部署策略?

• How will it fit into our automated deployment strategy?

如果对这些问题中的任何一个的回答是否定的,则有多种可能的回答——我们将在第 11 章中更详细地讨论它们。

If the answer to any of these questions is in any way negative, there are various possible responses—we discuss them at greater length in Chapter 11.

处于正确部署状态的环境在配置管理术语中称为基线。您的自动化环境供应系统应该能够建立或重新建立项目近期历史中存在的任何给定基线。每当您更改应用程序宿主环境的任何方面时,您都应该存储更改,创建新版本的基线并将该应用程序版本与新版本的基线相关联。这可确保您下次部署应用程序或创建新环境时,它会包含更改。

An environment that is in a properly deployed state is known as a baseline in configuration management terminology. Your automated environment provisioning system should be able to establish, or reestablish, any given baseline that has existed in the recent history of your project. Any time you change any aspect of the host environment of your applications, you should store the change, creating a new version of the baseline and associating that version of the application with the new version of the baseline. This ensures that the next time that you deploy the application or create a new environment, it will include the change.

从本质上讲,您应该像对待代码一样对待您的环境——逐步更改它并将更改检查到版本控制中。应对每项更改进行测试,以确保它不会破坏在新版本环境中运行的任何应用程序。

Essentially, you should treat your environment the same way you treat your code—changing it incrementally and checking the changes into version control. Every change should be tested to ensure that it doesn’t break any of the applications that run in the new version of the environment.

管理环境的工具

Tools to Manage Environments

Puppet 和 CfEngine 是使以自动化方式管理操作系统配置成为可能的两个工具示例。使用这些工具,您可以声明性地定义诸如哪些用户应该有权访问您的盒子以及应该安装什么软件等内容。这些定义可以存储在您的版本控制系统中。在您的系统上运行的代理会定期提取最新配置并更新操作系统和安装在其上的软件。有了这样的系统,就没有理由登录到一个盒子来进行修复:所有更改都可以通过版本控制系统启动,因此您可以完整记录每次更改 - 何时以及由谁进行。

Puppet and CfEngine are two examples of tools that make it possible to manage operating system configuration in an automated fashion. Using these tools, you can declaratively define things such as which users should have access to your boxes and what software should be installed. These definitions can be stored in your version control system. Agents running on your systems regularly pull the latest configuration and update the operating system and the software installed on it. With systems like these, there is no reason to log into a box to make fixes: All changes can be initiated through the version control system, so you have a complete record of every change—when it was made and by whom.

虚拟化还可以提高环境管理过程的效率。无需使用自动化过程从头开始创建新环境,您只需复制环境中的每个框并将其存储为基线。然后创建新环境就很简单了——只需单击一个按钮即可完成。虚拟化还有其他好处,例如整合硬件和标准化硬件平台的能力,即使您的应用程序需要异构环境也是如此。

Virtualization can also improve the efficiency of the environment management process. Instead of creating a new environment from scratch using an automated process, you can simply take a copy of each box in your environment and store it as a baseline. Then it is trivial to create new environments—it can be done by clicking a button. Virtualization has other benefits, such as the ability to consolidate hardware and to standardize your hardware platform even if your applications require heterogeneous environments.

我们将在第 11 章“管理基础设施和环境”中更详细地讨论这些工具。

We discuss these tools in more detail in Chapter 11, “Managing Infrastructure and Environments.”

管理变革过程

Managing the Change Process

最后,必须能够管理对环境进行更改的过程。生产环境应该完全锁定。如果不通过您组织的变更管理流程,任何人都不可能对其进行更改。原因很简单:即使是微小的变化也可能破坏它。更改必须在投入生产之前进行测试,为此应该编写脚本并将其签入版本控制。然后,一旦更改获得批准,就可以以自动化方式将其部署到生产环境中。

Finally, it is essential to be able to manage the process of making changes to your environments. A production environment should be completely locked down. It should not be possible for anybody to make a change to it without going through your organization’s change management process. The reason for this is simple: Even a tiny change could break it. A change must be tested before it goes into production, and for that it should be scripted and checked into version control. Then, once the change has been approved, it can be rolled out to the production environments in an automated fashion.

从这个意义上说,对环境的更改就像对软件的更改一样。它必须以与更改应用程序代码完全相同的方式经历您的构建、部署、测试和发布过程。

In this sense, a change to your environment is just like a change to your software. It has to go through your build, deploy, test, and release process in exactly the same way as a change to the application’s code.

在这方面,测试环境应该与生产环境一样对待。批准过程通常会更简单——它应该掌握在管理测试环境的人员手中——但在所有其他方面,他们的配置管理是相同的。这很重要,因为这意味着您正在测试在更频繁地部署到测试环境期间用于管理生产环境的流程。它承载着重复您的测试环境在软件配置方面应该与您的生产环境非常相似——这样当您部署到生产环境时就不会出现意外。这并不意味着测试环境必须是昂贵的生产环境的克隆;相反,它们应该由相同的机制进行管理、部署和配置。

In this respect, testing environments should be treated the same as production environments. The approval process will usually be simpler—it should be in the hands of the people managing the testing environment—but in all other respects their configuration management is the same. This is essential because it means that you are testing the process that you use to manage your production environments during the more frequent deployments into test environments. It bears repeating that your test environments should closely resemble your production environments in terms of software configuration—that way there should be no surprises when you deploy to production. This does not imply that test environments must be clones of expensive production environments; rather, that they should be managed, deployed to, and configured by the same mechanisms.

概括

Summary

配置管理是本书其他内容的基础。没有它就不可能进行持续集成、发布管理和部署流水线。它还对交付团队内部的协作产生了巨大的积极影响。正如我们希望我们已经阐明的那样,这不仅仅是选择和实施工具的问题,尽管这很重要;至关重要的是,这也是一个将良好做法落实到位的问题。

Configuration management is the foundation of everything else in this book. It is impossible to do continuous integration, release management, and deployment pipelining without it. It also makes a huge positive impact on collaboration within delivery teams. As we hope we have made clear, it is not just a question of choosing and implementing a tool, although that is important; it is also, crucially, a question of putting good practices into place.

如果您的配置管理流程健全,您应该能够对以下问题回答“是”:

If your configuration management process is sound, you should be able to answer “yes” to the following questions:

• 您能否从您存储的受版本控制的资产中从头开始完全重新创建您的生产系统(不包括生产数据)?

• Could you completely re-create your production system, excluding production data, from scratch from the version-controlled assets that you store?

• 您能否退回到应用程序较早的、已知的良好状态?

• Could you regress to an earlier, known good state of your application?

• 您能确定生产、暂存和测试中的每个已部署环境都以完全相同的方式设置吗?

• Can you be sure that each deployed environment in production, in staging, and in test is set up in precisely the same way?

否则,您的组织将面临风险。特别是,我们建议制定存储基线和控制更改的策略:

If not, then your organization is at risk. In particular, we recommend having a strategy for storing baselines and controlling changes to:

• 您的应用程序的源代码、构建脚本、测试、文档、需求、数据库脚本、库和配置文件

• Your applications’ source code, build scripts, tests, documentation, requirements, database scripts, libraries, and configuration files

• 您的开发、测试和运营工具链

• Your development, testing, and operations toolchains

• 开发、测试和生产中使用的所有环境

• All environments used in development, testing, and production

• 与您的应用程序关联的整个应用程序栈——包括二进制文件和配置

• The entire application stack associated with your applications—both binaries and configuration

• 在整个应用程序生命周期(构建、部署、测试、操作)中,与每个应用程序在其运行的每个环境中相关联的配置

• The configuration associated with every application in every environment it runs in, across the entire application lifecycle (building, deployment, testing, operation)

第 3 章持续集成

Chapter 3. Continuous Integration

介绍

Introduction

许多软件项目的一个极其奇怪但普遍的特征是,在开发过程中,应用程序在很长一段时间内都没有处于工作状态。事实上,大多数由大型团队开发的软件都将很大一部分开发时间花在了无法使用的状态上。这样做的原因很容易理解:没有人有兴趣尝试运行整个应用程序直到它完成。开发人员检查更改,甚至可能运行自动化单元测试,但没有人尝试真正启动应用程序并在类似生产的环境中使用它。

An extremely strange, but common, feature of many software projects is that for long periods of time during the development process the application is not in a working state. In fact, most software developed by large teams spends a significant proportion of its development time in an unusable state. The reason for this is easy to understand: Nobody is interested in trying to run the whole application until it is finished. Developers check in changes and might even run automated unit tests, but nobody is trying to actually start the application and use it in a production-like environment.

在使用长寿命分支或将验收测试推迟到最后的项目中,情况更是如此。许多此类项目在开发结束时安排了冗长的集成阶段,以便开发团队有时间合并分支并使应用程序正常运行,以便可以进行验收测试。更糟糕的是,一些项目发现当他们到达这个阶段时,他们的软件并不适合他们的目的。这些整合期可能会持续很长时间,最糟糕的是,没有人能够预测多长时间。

This is doubly true in projects that use long-lived branches or defer acceptance testing until the end. Many such projects schedule lengthy integration phases at the end of development to allow the development team time to get the branches merged and the application working so it can be acceptance-tested. Even worse, some projects find that when they get to this phase, their software is not fit for purpose. These integration periods can take an extremely long time, and worst of all, nobody has any way to predict how long.

另一方面,我们已经看到项目最多花费几分钟的时间处于其应用程序无法使用最新更改的状态。区别在于持续集成的使用。持续集成要求每次有人提交任何更改时,都会构建整个应用程序并对其运行一组全面的自动化测试。至关重要的是,如果构建或测试过程失败,开发团队会停止他们正在做的任何事情并立即解决问题。持续集成的目标是软件始终处于工作状态。

On the other hand, we have seen projects that spend at most a few minutes in a state where their application is not working with the latest changes. The difference is the use of continuous integration. Continuous integration requires that every time somebody commits any change, the entire application is built and a comprehensive set of automated tests is run against it. Crucially, if the build or test process fails, the development team stops whatever they are doing and fixes the problem immediately. The goal of continuous integration is that the software is in a working state all the time.

持续集成最早出现在 Kent Beck 的《Extreme Programming Explained 》一书中(1999 年首次出版)。与其他极限编程实践一样,持续集成背后的想法是,如果定期集成您的代码库很好,为什么不一直这样做呢?在集成的上下文中,“始终”意味着每次有人提交任何更改为版本控制系统。正如我们的一位同事 Mike Roberts 所说,“持续比你想象的更频繁”[aEu8Nu]。

Continuous integration was first written about in Kent Beck’s book Extreme Programming Explained (first published in 1999). As with other Extreme Programming practices, the idea behind continuous integration was that, if regular integration of your codebase is good, why not do it all the time? In the context of integration, “all the time” means every single time somebody commits any change to the version control system. As one of our colleagues, Mike Roberts, says, “Continuously is more often than you think” [aEu8Nu].

持续集成代表了范式转变。如果没有持续集成,您的软件就会被破坏,直到有人证明它可以工作,通常是在测试或集成阶段。通过持续集成,您的软件被证明可以在每一个新的变化中工作(假设有一套足够全面的自动化测试)——而且您知道它崩溃的那一刻并可以立即修复它。有效使用持续集成的团队能够比不使用持续集成的团队更快地交付软件,并且错误更少。当修复成本更低时,错误会在交付过程中更早地被发现,从而显着节省成本和时间。因此,我们认为这是专业团队的基本实践,也许与使用版本控制一样重要。

Continuous integration represents a paradigm shift. Without continuous integration, your software is broken until somebody proves it works, usually during a testing or integration stage. With continuous integration, your software is proven to work (assuming a sufficiently comprehensive set of automated tests) with every new change—and you know the moment it breaks and can fix it immediately. The teams that use continuous integration effectively are able to deliver software much faster, and with fewer bugs, than teams that do not. Bugs are caught much earlier in the delivery process when they are cheaper to fix, providing significant cost and time savings. Hence we consider it an essential practice for professional teams, perhaps as important as using version control.

本章的其余部分描述了如何实施持续集成。我们将解释如何解决随着您的项目变得更加复杂而出现的常见问题,列出支持持续集成的有效实践及其对设计和开发过程的影响。我们还将讨论更高级的主题,包括如何与分布式团队进行 CI。

The rest of this chapter describes how to implement continuous integration. We’ll explain how to solve common problems that occur as your project becomes more complex, listing effective practices that support continuous integration and its effects on the design and development process. We’ll also discuss more advanced topics, including how to do CI with distributed teams.

持续集成在这本书的姊妹篇中有详细论述:Paul Duvall 的书持续集成(Addison-Wesley,2006 年)。如果你想要比我们在本章中提供的更多的细节,那就是去的地方。

Continuous integration is dealt with at length in a companion volume to this one: Paul Duvall’s book Continuous Integration (Addison-Wesley, 2006). If you want more detail than we provide in this chapter, that is the place to go.

本章主要针对开发人员。然而,它也包含了一些我们认为对想要了解更多持续集成实践的项目经理有用的信息。

This chapter is mainly aimed at developers. However, it also contains some information that we think will be useful for project managers who want to know more about the practice of continuous integration.

实施持续集成

Implementing Continuous Integration

持续集成的实践依赖于特定的先决条件。我们将介绍这些,然后查看可用的工具。也许最重要的是,持续集成取决于团队遵循一些基本实践,因此我们将花一些时间讨论这些。

The practice of continuous integration relies on certain prerequisites being in place. We’ll cover these, then look at the tools available. Perhaps most importantly, continuous integration depends on teams following a few essential practices, so we’ll spend some time discussing these.

开始之前你需要什么

What You Need Before You Start

在开始持续集成之前,您需要做三件事。

There are three things that you need before you can start with continuous integration.

1.版本控制

1. Version Control

项目中的所有内容都必须签入单个版本控制存储库:代码、测试、数据库脚本、构建和部署脚本,以及创建、安装、运行和测试应用程序所需的任何其他内容。这听起来很明显,但令人惊讶的是,仍然有一些项目不使用任何形式的版本控制。有些人不认为他们的项目大到足以保证使用版本控制。我们不相信有一个小到可以做的项目没有它。我们自己写代码的时候,为了自己的需要在自己的电脑上,还是会用到版本控制。存在几个简单、强大、轻量级和免费的版本控制系统。

Everything in your project must be checked in to a single version control repository: code, tests, database scripts, build and deployment scripts, and anything else needed to create, install, run, and test your application. This may sound obvious, but surprisingly, there are still projects that don’t use any form of version control. Some people don’t consider their project big enough to warrant the use of version control. We don’t believe that there is a project small enough to do without it. When we write code on our own, for our own needs on our own computers, we still use version control. There exist several simple, powerful, lightweight, and free version control systems.

我们在第 32 页的“使用版本控制”部分第 14 章“高级版本控制”中更详细地描述了版本控制系统的选择和使用。

We describe the choice and use of revision control systems in more detail in the “Using Version Control” section on page 32 and in Chapter 14, “Advanced Version Control.”

2. 自动化构建

2. An Automated Build

您必须能够从命令行开始构建。您可以从命令行程序开始,告诉您的 IDE 构建您的软件,然后运行您的测试,或者它可以是相互调用的多阶段构建脚本的复杂集合。无论采用何种机制,人或计算机都必须能够通过命令行以自动化方式运行您的构建、测试和部署过程。

You must be able to start your build from the command line. You can start off with a command-line program that tells your IDE to build your software and then runs your tests, or it can be a complex collection of multistage build scripts that call one another. Whatever the mechanism, it must be possible for either a person or a computer to run your build, test, and deployment process in an automated fashion via the command line.

如今,IDE 和持续集成工具变得相当复杂,您通常可以构建软件并运行测试,而无需靠近命令行。但是,我们认为您仍然应该拥有可以在没有 IDE 的情况下通过命令行运行的构建脚本。这可能看起来有争议,但有几个原因:

IDEs and continuous integration tools have become pretty sophisticated these days, and you can usually build your software and run tests without going anywhere near the command line. However, we think that you should still have build scripts that can be run via the command line without your IDE. This might seem controversial, but there are several reasons for this:

• 您需要能够在持续集成环境中以自动化方式运行您的构建过程,以便在出现问题时对其进行审计。

• You need to be able to run your build process in an automated way from your continuous integration environment so that it can be audited when things go wrong.

• 你的构建脚本应该像你的代码库一样对待。它们应该经过测试并不断重构,以便它们整洁且易于理解。使用 IDE 生成的构建过程不可能做到这一点。项目变得越复杂,这一点就变得越来越重要。

• Your build scripts should be treated like your codebase. They should be tested and constantly refactored so that they are tidy and easy to understand. It’s impossible to do this with an IDE-generated build process. This gets more and more important the more complex the project becomes.

• 它使构建的理解、维护和调试变得更容易,并允许与操作人员更好地协作。

• It makes understanding, maintaining, and debugging the build easier, and allows for better collaboration with operations people.

3.团队协议

3. Agreement of the Team

持续集成是一种实践,而不是一种工具。它需要您的开发团队一定程度的承诺和纪律。你需要每个人经常检查主线的小增量变化,并同意项目的最高优先级任务是修复任何破坏应用程序的变化。如果人们不采用它工作所必需的纪律,那么您在持续集成方面的尝试将不会带来您希望的质量改进。

Continuous integration is a practice, not a tool. It requires a degree of commitment and discipline from your development team. You need everyone to check in small incremental changes frequently to mainline and agree that the highest priority task on the project is to fix any change that breaks the application. If people don’t adopt the discipline necessary for it to work, your attempts at continuous integration will not lead to the improvement in quality that you hope for.

一个基本的持续集成系统

A Basic Continuous Integration System

你不需要一个持续集成软件来进行持续集成——正如我们所说,它是一种实践,而不是一种工具。詹姆斯肖尔描述了在一篇名为“每天一美元的持续集成”[bAJpjp] 的文章中,开始持续集成的最简单方法仅使用未使用的开发机器、橡皮鸡和铃铛。这篇文章值得一读,因为它精彩地展示了 CI 的本质,除了版本控制之外没有任何工具。

You don’t need a continuous integration software in order to do continuous integration—as we say, it is a practice, not a tool. James Shore describes the simplest way to get started with continuous integration in an article called “Continuous Integration on a Dollar a Day” [bAJpjp] using only an unused development machine, a rubber chicken, and a bell. It’s worth reading this article because it demonstrates wonderfully the essentials of CI without any tool except version control.

但实际上,如今的 CI 工具安装和运行起来非常简单。有几个开源选项,例如 Hudson 和古老的 CruiseControl 系列(CruiseControl、CruiseControl.NET 和 CruiseControl.rb)。特别是 Hudson 和 CruiseControl.rb 非常容易启动和运行。CruiseControl.rb 非常轻量,任何对 Ruby 有一定了解的人都可以轻松扩展。Hudson 拥有大量插件,使其能够与构建和部署生态系统中的几乎所有工具集成。

In reality, though, CI tools these days are extremely simple to install and get running. There are several open source options, such as Hudson and the venerable CruiseControl family (CruiseControl, CruiseControl.NET, and CruiseControl.rb). Hudson and CruiseControl.rb in particular are extremely straightforward to get up and running. CruiseControl.rb is very lightweight and can be easily extended by anyone with some knowledge of Ruby. Hudson has a large pool of plugins allowing it to integrate with pretty much every tool in the build and deployment ecosystem.

在撰写本文时,两个商业 CI 服务器具有专为小型团队设计的免费版本:ThoughtWorks Studios 的 Go 和 JetBrains 的 TeamCity。其他流行的商业 CI 服务器包括 Atlassian 的 Bamboo 和 Zutubi 的 Pulse。也可用于简单 CI 的高端发布管理和构建加速系统包括 UrbanCode 的 AntHillPro、ElectricCloud 的 ElectricCommander 和 IBM 的 BuildForge。那里有更多的系统;有关完整列表,请转到 CI 特征矩阵 [bHOgH4]。

At the time of writing, two commercial CI servers had free editions designed for small teams: Go from ThoughtWorks Studios and TeamCity from JetBrains. Other popular commercial CI servers include Atlassian’s Bamboo and Zutubi’s Pulse. High-end release management and build acceleration systems which can also be used for plain and simple CI include UrbanCode’s AntHillPro, ElectricCloud’s ElectricCommander, and IBM’s BuildForge. There are plenty more systems out there; for a complete list, go to the CI feature matrix [bHOgH4].

一旦你安装了你选择的 CI 工具,考虑到上述先决条件,应该可以在几分钟内开始,告诉你的工具在哪里可以找到你的源代码控制存储库,运行什么脚本来编译,如果必要的,并为您的应用程序运行自动提交测试,以及如何告诉您最后一组更改是否破坏了软件。

Once you have your CI tool of choice installed, given the preconditions described above, it should be possible to get started in just a few minutes by telling your tool where to find your source control repository, what script to run in order to compile, if necessary, and run the automated commit tests for your application, and how to tell you if the last set of changes broke the software.

第一次在 CI 工具上运行构建时,您可能会发现运行 CI 工具的机器缺少一堆软件和设置。这是一个独特的学习机会——记下您为使事情正常进行所做的一切,并将其放在项目的 wiki 上。您应该花时间将系统依赖的任何软件或设置检查到版本控制中,并自动执行配置新机器的过程。

The first time you run your build on a CI tool, you are likely to discover that the box you’re running your CI tool on is missing a stack of software and settings. This is a unique learning opportunity—make a note of everything that you did to get things working, and put it on your project’s wiki. You should take the time to check any software or settings that your system depends on into version control and automate the process of provisioning a new box.

下一步是让每个人都开始使用 CI 服务器。这是一个简单的过程。

The next step is for everybody to start using the CI server. Here is a simple process to follow.

准备好签入最新更改后:

Once you’re ready to check in your latest change:

1. 检查构建是否已经在运行。如果是这样,请等待它完成。如果失败,您需要与团队的其他成员一起工作,在签入之前将其变为绿色。

1. Check to see if the build is already running. If so, wait for it to finish. If it fails, you’ll need to work with the rest of the team to make it green before you check in.

2. 完成并通过测试后,从版本控制存储库中的此版本更新开发环境中的代码以获取任何更新。

2. Once it has finished and the tests have passed, update the code in your development environment from this version in the version control repository to get any updates.

3. 在您的开发机器上运行构建脚本和测试以确保一切在您的计算机上仍然正常工作,或者使用您的 CI 工具的个人构建功能。

3. Run the build script and tests on your development machine to make sure that everything still works correctly on your computer, or alternatively use your CI tool’s personal build feature.

4. 如果您的本地构建通过,请将您的代码签入版本控制。

4. If your local build passes, check your code into version control.

5. 等待您的 CI 工具使用您的更改运行构建。

5. Wait for your CI tool to run the build with your changes.

6. 如果失败,请停止您正在做的事情并立即在您的开发机器上修复问题——转到第 3 步。

6. If it fails, stop what you’re doing and fix the problem immediately on your development machine—go to step 3.

7. 如果构建通过,高兴并继续你的下一个任务。

7. If the build passes, rejoice and move on to your next task.

如果团队中的每个人在每次提交任何更改时都遵循这些简单的步骤,您就会知道您的软件始终可以在与 CI 框具有相同配置的任何框上运行。

If everybody on the team follows these simple steps every time they commit any change, you will know that your software works on any box with the same configuration as the CI box at all times.

持续集成的先决条件

Prerequisites for Continuous Integration

持续集成不会自行修复您的构建过程。事实上,如果你在项目中期开始这样做,可能会非常痛苦。为了使 CI 有效,在开始之前需要采取以下做法。

Continuous integration won’t fix your build process on its own. In fact, it can be very painful if you start doing it midproject. For CI to be effective, the following practices will need to be in place before you start.

定期入住

Check In Regularly

持续集成正常工作的最重要实践是频繁签入主干或主线。您应该每天至少检查几次代码。

The most important practice for continuous integration to work properly is frequent check-ins to trunk or mainline. You should be checking in your code at least a couple of times a day.

定期签到还有很多其他好处。它使您的更改更小,因此不太可能破坏构建。这意味着当您犯错或走错路时,您可以使用该软件的最新已知良好版本来恢复。它可以帮助您在重构时更加自律,并坚持保留行为的小改动。它有助于确保更改大量文件的更改不太可能与其他人的工作发生冲突。它允许开发人员更具探索性,尝试想法并通过恢复到最后提交的版本来丢弃它们。它会迫使您定期休息并伸展肌肉,以帮助避免腕管综合症或 RSI。这也意味着如果发生灾难性的事情(例如误删某些东西),您并没有损失太多工作。

Checking in regularly brings lots of other benefits. It makes your changes smaller and thus less likely to break the build. It means you have a recent knowngood version of the software to revert to when you make a mistake or go down the wrong path. It helps you to be more disciplined about your refactoring and stick to small changes that preserve behavior. It helps to ensure that changes altering a lot of files are less likely to conflict with other people’s work. It allows developers to be more explorative, trying out ideas and discarding them by reverting back to the last committed version. It forces you to take regular breaks and stretch your muscles to help avoid carpal tunnel syndrome or RSI. It also means that if something catastrophic happens (such as deleting something by mistake) you haven’t lost too much work.

我们提到故意检查主干。许多项目在版本控制中使用分支来管理大型团队。但是在使用分支的同时真正进行持续集成是不可能的,因为根据定义,如果你在一个分支上工作,你的代码就不会与其他开发人员的代码集成。使用长期分支的团队面临与我们在本章开头描述的完全相同的集成问题。除非在非常有限的情况下,否则我们不建议使用分支。第 14 章“高级版本控制”中对这些问题进行了更详细的讨论。

We mention checking into trunk on purpose. Many projects use branches in version control to manage large teams. But it is impossible to truly do continuous integration while using branches because, by definition, if you are working on a branch, your code is not being integrated with that of other developers. Teams who use long-lived branches face exactly the same integration problems as we described at the beginning of this chapter. We can’t recommend using branches except in very limited circumstances. There is a much more detailed discussion of these issues in Chapter 14, “Advanced Version Control.”

创建一个全面的自动化测试套件

Create a Comprehensive Automated Test Suite

如果您没有一套全面的自动化测试,通过构建仅意味着可以编译和组装应用程序。虽然对于某些团队来说这是一大步,但必须进行一定程度的自动化测试以确保您的应用程序确实在运行。自动化测试有很多种,我们将在下一章中更详细地讨论它们。但是,我们有兴趣从我们的持续集成构建中运行三种测试:单元测试、组件测试和验收测试。

If you don’t have a comprehensive suite of automated tests, a passing build only means that the application could be compiled and assembled. While for some teams this is a big step, it’s essential to have some level of automated testing to provide confidence that your application is actually working. There are many kinds of automated tests, and we discuss them in more detail in the next chapter. However, there are three kinds of tests we are interested in running from our continuous integration build: unit tests, component tests, and acceptance tests.

编写单元测试是为了单独测试应用程序的小部分的行为(例如,一个方法或一个函数,或者它们中的一小部分之间的交互)。它们通常可以在不启动整个应用程序的情况下运行。它们不会访问数据库(如果您的应用程序有数据库)、文件系统或网络。它们不要求您的应用程序在类似生产的环境中运行。单元测试应该运行得非常快——你的整个套件,即使是大型应用程序,也应该能够在十分钟内运行。

Unit tests are written to test the behavior of small pieces of your application in isolation (say, a method, or a function, or the interactions between a small group of them). They can usually be run without starting the whole application. They do not hit the database (if your application has one), the filesystem, or the network. They don’t require your application to be running in a production-like environment. Unit tests should run very fast—your whole suite, even for a large application, should be able to run in under ten minutes.

组件测试测试应用程序的多个组件的行为。与单元测试一样,它们并不总是需要启动整个应用程序。但是,它们可能会攻击数据库、文件系统或其他系统(可能已被清除)。组件测试通常需要更长的时间才能运行。

Component tests test the behavior of several components of your application. Like unit tests, they don’t always require starting the whole application. However, they may hit the database, the filesystem, or other systems (which may be stubbed out). Component tests typically take longer to run.

验收测试测试应用程序是否满足业务决定的验收标准,包括应用程序提供的功能及其特性,如容量、可用性、安全性等。验收测试最好以这样的方式编写,即它们在类似生产的环境中针对整个应用程序运行。验收测试可能需要很长时间才能运行——验收测试套件连续运行需要一天以上的情况并非闻所未闻。

Acceptance tests test that the application meets the acceptance criteria decided by the business, including both the functionality provided by the application and its characteristics such as capacity, availability, security, and so on. Acceptance tests are best written in such a way that they run against the whole application in a production-like environment. Acceptance tests can take a long time to run—it’s not unheard of for an acceptance test suite to take more than a day to run sequentially.

这三组测试相结合,应该提供极高的置信度,即任何引入的更改都不会破坏现有功能。

These three sets of tests, combined, should provide an extremely high level of confidence that any introduced change has not broken existing functionality.

保持构建和测试过程简短

Keep the Build and Test Process Short

如果构建代码和运行单元测试的时间太长,您将遇到以下问题:

If it takes too long to build the code and run the unit tests, you will run into the following problems:

• 人们将在签入之前停止进行完整构建和运行测试。您将开始获得更多失败的构建。

• People will stop doing a full build and running the tests before they check in. You will start to get more failing builds.

• 持续集成过程将花费很长时间,以至于在您再次运行构建时将发生多次提交,因此您不知道是哪个签入破坏了构建。

• The continuous integration process will take so long that multiple commits will have taken place by the time you can run the build again, so you won’t know which check-in broke the build.

• 人们签到的频率会降低,因为他们不得不长时间坐在那里等待软件构建和测试运行。

• People will check in less often because they have to sit around for ages waiting for the software to build and the tests to run.

理想情况下,您在签入之前和在 CI 服务器上运行的编译和测试过程应该不会超过几分钟。我们认为十分钟是极限,五分钟更好,九十秒左右是最理想的。对于习惯于处理小项目的人来说,十分钟似乎是一段很长的时间。对于经历过长达一个小时的编译的老朋友来说,这似乎是一个非常短的时间。它大约是您可以用来泡杯茶、快速聊天、查看电子邮件或伸展肌肉的时间。

Ideally, the compile and test process that you run prior to check-in and on your CI server should take no more than a few minutes. We think that ten minutes is about the limit, five minutes is better, and about ninety seconds is ideal. Ten minutes will seem like a long time to people used to working on small projects. It will seem like a very short time to old-timers who have experienced hour-long compiles. It’s around the amount of time you can devote to making a cup of tea, a quick chat, checking your email, or stretching your muscles.

这一要求似乎与之前的要求相矛盾——拥有一套全面的自动化测试。但是您可以使用多种技术来减少构建时间。首先要考虑的是让你的测试运行得更快。XUnit 类型的工具,例如 JUnit 和 NUnit,提供了每个测试在其输出中花费的时间的细分。找出哪些测试执行缓慢,看看是否有优化它们的方法,或者以更少的处理量获得相同的覆盖率和对代码的信心。这是您应该定期执行的练习。

This requirement may seem to contradict the previous one—having a comprehensive set of automated tests. But there are a number of techniques that you can use to reduce the build time. The first thing to consider is making your tests run faster. XUnit-type tools, such as JUnit and NUnit, provide a breakdown of how long each test took in their output. Find out which tests are performing slowly, and see if there’s a way to optimize them or get the same coverage and confidence in your code with less processing. This is a practice that you should perform regularly.

但是,在某些时候,您需要将测试过程分成多个阶段,如第 5 章“部署管道剖析”中所述。你如何拆分它们?您的第一个动作应该是创建两个阶段。应该编译软件,运行单元测试套件来测试构成应用程序的各个类,并创建可部署的二进制文件。这个阶段称为提交阶段。我们将在第 7 章中详细介绍构建的这个阶段。

However, at some point you will need to split your test process into multiple stages, as described in detail in Chapter 5, “Anatomy of the Deployment Pipeline.” How do you split them up? Your first action should be creating two stages. One should compile the software, run your suite of unit tests that test individual classes making up your application, and create a deployable binary. This stage is called the commit stage. We go into a great deal of detail about this stage of your build in Chapter 7.

第二阶段应该从第一阶段获取二进制文件并运行验收测试,以及集成测试和性能测试(如果有的话)。现代 CI 服务器可以轻松地以这种方式创建分阶段构建、同时运行多个任务并汇总结果,以便您可以一目了然地查看构建状态。

The second stage should take the binaries from the first stage and run the acceptance tests, as well as integration tests, and performance tests if you have them. Modern CI servers make it easy to create staged builds in this way, run multiple tasks concurrently, and aggregate the results up so you can see the state of your build at a glance.

提交阶段应该在签入之前运行,并且应该在每次签入时在 CI 服务器上运行。运行验收测试的阶段应该在签入测试套件通过后运行,但可能需要更长的时间。如果您发现第二次构建花费的时间超过半小时左右,您应该考虑在更大的多处理器机器上并行运行这个测试套件,或者建立一个构建网格。现代 CI 系统使这变得简单。将一个简单的冒烟测试套件合并到您的提交阶段通常很有用。这个冒烟测试应该执行一些简单的验收和集成测试,以确保最常用的功能没有被破坏——如果是,则迅速报告。

The commit stage should be run before checking in, and should run on the CI server for every check-in. The stage that runs the acceptance tests should be run once the check-in test suite passes, but can take a longer time. If you find that the second build takes longer than half an hour or so, you should consider running this test suite in parallel on a larger multiprocessor box, or perhaps establish a build grid. Modern CI systems make this simple. It is often useful to incorporate a simple smoke test suite into your commit stage. This smoke test should perform a few simple acceptance and integration tests to make sure that the most commonly used functionality isn’t broken—and report back quickly if it is.


图片

通常希望将您的验收测试分组到功能区域中。这允许您在对某个区域进行更改后,运行侧重于系统行为特定方面的测试集合。许多单元测试框架允许您以这种方式对测试进行分类。

It is often desirable to group your acceptance tests into functional areas. This allows you to run collections of tests that focus on particular aspects of the behavior of your system after making a change in that area. Many unit testing frameworks allow you to categorize your tests in this way.


您可能会遇到需要将项目拆分为多个模块的阶段,每个模块在功能上都是独立的。这需要仔细考虑如何在修订控制和 CI 服务器上组织这些子项目。我们将在第 13 章“管理组件和依赖项”中更详细地讨论这个问题。

You may get to a stage where your project needs to be split up into several modules, each of which is functionally independent. This requires some careful thought in terms of how you organize these subprojects both in revision control and on your CI server. We’ll deal with this in more detail in Chapter 13, “Managing Components and Dependencies.”

管理您的开发工作区

Managing Your Development Workspace

仔细管理开发环境对于开发人员的生产力和理智来说很重要。当开发人员开始一项新的工作时,他们应该始终从一个已知的良好起点开始工作。他们应该能够运行构建、执行自动化测试,并在他们控制的环境中部署应用程序。一般来说,这应该在他们自己的本地机器上。只有在特殊情况下,您才应该使用共享环境进行开发。在本地开发环境中运行应用程序应该使用在持续集成和测试环境中以及最终在生产环境中使用的相同自动化流程。

It is important for developers’ productivity and sanity that their development environment is carefully managed. Developers should always work from a knowngood starting point when they begin a fresh piece of work. They should be able to run the build, execute the automated tests, and deploy the application in an environment under their control. In general, this should be on their own local machine. Only in exceptional circumstances should you use shared environments for development. Running the application in a local development environment should use the same automated processes that are used in the continuous integration and testing environments and ultimately in production.

实现这一点的第一个先决条件是仔细的配置管理,不仅是源代码,还有测试数据、数据库脚本、构建脚本和部署脚本。所有这些都必须存储在版本控制中,并且这些最新的已知良好版本应该是编码开始时的起点。在此上下文中,“已知良好”意味着您正在使用的修订版已通过持续集成服务器上的所有自动化测试。

The first prerequisite to achieve this is careful configuration management, not just of source code, but also of test data, database scripts, build scripts, and deployment scripts. All of these must be stored in version control, and the most recent known-good version of these should be the starting point when coding begins. In this context, “known-good” means that the revision you are working from has passed all automated tests on your continuous integration server.

第二步是第三方依赖、库、组件的配置管理。拥有所有库或组件的正确版本至关重要,这意味着已知的相同版本可以与您正在使用的源代码版本一起使用。有一些开源工具可以帮助管理第三方依赖项,Maven 和 Ivy 是最常见的。但是,在使用这些工具时,您需要小心确保它们的配置正确,这样您就不会总是在本地工作副本中获得某些依赖项的最新可用版本。

The second step is configuration management of third-party dependencies, libraries, and components. It is vital that you have the correct versions of all libraries or components, which means the same versions that are know to work with the version of the source code you are working from. There are open source tools to help manage third-party dependencies, Maven and Ivy being the most common. However, when working with these tools you need to be careful to make sure they are configured correctly so you don’t always get the latest available version of some dependency in your local working copy.

对于大多数项目,它们所依赖的第三方库不会经常更改,因此最简单的解决方案是将这些库与源代码一起提交到您的版本控制系统中。在第 13 章“管理组件和依赖项”中有关于所有这些的更多信息。

For most projects, the third-party libraries they depend on don’t change very frequently, so the simplest solution of all is to commit these libraries into your version control system along with your source code. There is more information on all this in Chapter 13, “Managing Components and Dependencies.”

最后一步是确保自动化测试(包括冒烟测试)可以在开发人员机器上运行。在大型系统上,这可能涉及配置中间件系统和运行内存或单用户版本的数据库。这可能需要一定程度的努力,但允许开发人员在每次签入之前针对开发人员机器上的工作系统运行冒烟测试会对应用程序的质量产生巨大影响。事实上,一个好的应用程序架构的标志是它允许应用程序在开发机器上运行而不会遇到太多麻烦。

The final step is to make sure that the automated tests, including smoke tests, can be run on developer machines. On a large system this might involve configuring middleware systems and running in-memory or single-user versions of databases. This can involve a certain degree of effort, but enabling developers to run a smoke test against a working system on a developer machine prior to each check-in can make a huge difference to the quality of your application. In fact, one sign of a good application architecture is that it allows the application to be run without much trouble on a development machine.

使用持续集成软件

Using Continuous Integration Software

市场上有许多产品可以为您的自动化构建和测试过程提供基础设施。持续集成软件最基本的功能是轮询你的版本控制系统,看看是否有任何提交发生,如果有,检查软件的最新版本,运行你的构建脚本来编译软件,运行测试,以及然后通知你结果。

There are many products on the market that can provide the infrastructure for your automated build and test process. The most basic functionality of continuous integration software is to poll your version control system to see if any commits have occurred and, if so, check out the latest version of the software, run your build script to compile the software, run the tests, and then notify you of the results.

基本操作

Basic Operation

从本质上讲,持续集成服务器软件有两个组成部分。第一个是长时间运行的进程,它可以定期执行一个简单的工作流。第二个提供已运行过程的结果视图,通知您构建和测试运行的成功或失败,并提供对测试报告、安装程序等的访问。

At heart, continuous integration server software has two components. The first is a long-running process which can execute a simple workflow at regular intervals. The second provides a view of the results of the processes that have been run, notifies you of the success or failure of your build and test runs, and provides access to test reports, installers, and so on.

通常的 CI 工作流程会定期轮询您的版本控制系统。如果它检测到任何更改,它会将您的项目副本检出到服务器上的目录或构建代理上的目录。然后它将执行您指定的命令。通常,这些命令构建您的应用程序并运行相关的自动化测试。

The usual CI workflow polls your revision control system at regular intervals. If it detects any change, it will check out a copy of your project to a directory on the server, or to a directory on a build agent. It will then execute the commands you specify. Typically, these commands build your application and run the relevant automated tests.

图 3.1 Hudson 截图,作者 Kohsuke Kawaguchi

Figure 3.1 Screenshot of Hudson, by Kohsuke Kawaguchi

图片

大多数 CI 服务器包括一个 Web 服务器,它向您显示已运行的构建列表(图 3.1),并允许您查看定义每个构建成功或失败的报告。这个构建指令序列应该最终生成和存储生成的工件,例如二进制文件或安装包,以便测试人员和客户可以轻松下载最新的好版本的软件。大多数 CI 服务器都可以使用 Web 界面或通过简单的脚本进行配置。

Most CI servers include a web server that shows you a list of builds that have run (Figure 3.1) and allows you to look at the reports that define the success or failure of each build. This sequence of build instructions should culminate in the production and storage of the resulting artifacts such as binaries or installation packages, so that testers and clients can easily download the latest good version of the software. Most CI servers are configurable using a web interface or through simple scripts.

钟声和口哨声

Bells and Whistles

您可以使用 CI 包的工作流功能来完成基本功能之外的许多其他事情。例如,您可以获得发送到外部设备的最新构建的状态。我们已经看到人们使用红色和绿色熔岩灯来显示上次构建的状态,或者使用 CI 系统将状态发送到 Nabaztag 无线电子兔子。我们认识的一位开发人员具有一定的电子技术,他创造了一个由闪光灯和警报器组成的奢华塔,它会爆炸并开始行动,以指示复杂项目中各种构建的进度。另一个技巧是使用文本到语音来读出破坏构建的人的名字。一些持续集成服务器可以显示构建的状态,以及签入人员的头像——这可以显示在大屏幕上。

You can use your CI package’s workflow capabilities to do lots of other things beyond the basic functionality. For example, you can get the status of the most recent build sent to an external device. We’ve seen people use red and green lava lamps to show the status of the last build, or a CI system that sent the status to a Nabaztag wireless electronic rabbit. One developer we know, with some skill in electronics, created an extravagant tower of flashing lights and sirens which would explode into action to indicate the progress of various builds on a complex project. Another trick is to use text-to-speech to read out the name of the person who broke the build. Some continuous integration servers can display the status of the build, along with the avatars of the people who checked in—and this can be displayed on a big screen.

项目使用这些小工具的原因很简单:它们是让每个人都能一目了然地查看构建状态的好方法。可见性是使用 CI 服务器最重要的好处之一。大多数 CI 服务器软件都附带一个小部件,您可以将其安装在开发机器上,以在桌面一角显示构建状态。像这样的工具对于分散的团队或至少不在同一个房间里工作的团队特别有用。

Projects use gadgets like these for the simple reason: They’re a great way to allow everyone to see the status of the build at a glance. Visibility is one of the most important benefits of using a CI server. Most CI server software ships with a widget that you can install on your development machine to show you the status of the build in the corner of your desktop. Tools like this are especially useful for teams that are distributed, or at least not working in the same room together.

这种可见性的唯一缺点是,如果您的开发团队与他们的客户近距离合作,就像大多数敏捷项目中的情况一样,那么构建失败(过程的自然组成部分)可能会被视为问题的标志应用程序的质量。事实恰恰相反:每次构建失败时,都表明已发现问题,否则可能已将其投入生产。然而,这有时很难解释。经历过几次这种情况,包括在构建中断的时间比我们任何人喜欢的时间都长时与客户进行了一些艰难的对话,我们只能建议您保持高可见度构建监视器并努力解释它的真实性好处。当然,最好的答案是努力保持构建绿色。

The only drawback of such visibility is that if your development team is working in close quarters with their customers, as should be the case in most agile projects, build failures—a natural part of the process—may become regarded as a sign of problems with the quality of the application. The fact is that the reverse is true: Every time a build fails, it indicates that a problem has been found that may otherwise have made it into production. However, this can sometimes be hard to explain. Having been through this several times, including having some difficult conversations with clients when the build was broken for a longer period than any of us liked, we can only recommend that you keep the high-visibility build monitor and work hard at explaining its very real benefits. Of course, the best answer of all is to work hard to keep the build green.

您还可以获取构建过程以执行源代码分析。团队通常会确定测试覆盖率、代码重复、遵守编码标准、圈复杂度和其他健康指标,并将结果显示在每个构建的摘要页面上。您还可以运行程序来生成对象模型或数据库模式的图形。这都是关于可见性的。

You can also get your build process to perform analysis of your source code. Teams commonly determine test coverage, code duplication, adherence to coding standards, cyclomatic complexity, and other indications of health, and have the results displayed on the summary page for each build. You can also run programs to produce graphs of the object model or database schema. This is all about visibility.

今天的高级 CI 服务器可以在构建网格中分配工作,管理协作组件集合的构建和依赖关系,直接向项目管理跟踪系统报告,以及做许多其他有用的事情。

Today’s advanced CI servers can distribute work across a build grid, manage the builds and dependencies of collections of collaborating components, report directly into your project management tracking system, and do lots of other useful things.

基本实践

Essential Practices

到目前为止,我们所描述的大部分内容都与构建和部署的自动化有关。但是,这种自动化存在于人工过程的环境中。持续集成是一种实践,而不是一种工具,它取决于纪律以使其有效。保持持续集成系统的运行,特别是当您处理大型复杂的 CI 系统时,需要整个开发团队的高度纪律。

So far, much of what we have described has been related to the automation of building and deployment. However, that automation exists within an environment of human processes. Continuous integration is a practice, not a tool, and it depends upon discipline to make it effective. Keeping a continuous integration system operating, particularly when you are dealing with large and complex CI systems, requires a significant degree of discipline from the development team as a whole.

我们的 CI 系统的目标是确保我们的软件本质上一直在工作。为了确保情况如此,以下是我们在团队中强制执行的做法。稍后我们将讨论可选但可取的实践,但此处列出的实践对于持续集成的工作是强制性的。

The objective of our CI system is to ensure that our software is working, in essence, all of the time. In order to ensure that this is the case, here are the practices that we enforce on our teams. Later we will discuss practices that are optional but desirable, but those listed here are mandatory for continuous integration to work.

不要签入损坏的构建

Don’t Check In on a Broken Build

持续集成的主要错误是检查损坏的构建。如果构建中断,负责的开发人员将等待修复它。他们会尽快找出破损的原因并进行修复。如果我们采用这种策略,我们将始终处于找出导致损坏的原因并立即修复的最佳位置。如果我们的一位同事进行了签入并因此破坏了构建,那么为了获得修复它的最佳机会,他们将需要清楚地解决问题。他们不希望我们检查进一步的变化,触发新的构建,并把失败与更多问题结合起来。

The cardinal sin of continuous integration is checking in on a broken build. If the build breaks, the developers responsible are waiting to fix it. They identify the cause of the breakage as soon as possible and fix it. If we adopt this strategy, we will always be in the best position to work out what caused the breakage and fix it immediately. If one of our colleagues has made a check-in and broken the build as a result, then to have the best chance of fixing it, they will need a clear run at the problem. They don’t want us checking in further changes, triggering new builds, and compounding the failure with more problems.

当这个规则被打破时,修复构建不可避免地需要更长的时间。人们习惯于看到构建失败,很快你就会陷入构建一直处于崩溃状态的情况。这种情况一直持续到团队中的某个人决定已经足够了,随后需要付出巨大的努力才能使构建绿色化,然后整个过程重新开始。就在这项工作完成之后,是时候让大家聚在一起提醒他们遵循这个原则将确保绿色构建,从而始终确保软件工作正常。

When this rule is broken, it inevitably takes much longer for the build to be fixed. People get used to seeing the build broken, and very quickly you get into a situation where the build stays broken all of the time. This continues until somebody on the team decides that enough is enough, a Herculean effort ensues to get the build green, and the process starts all over again. Just after this work is finished it’s a great time to get everybody together to remind them that following this principle will ensure a green build, and thus working software, all of the time.

在提交之前总是在本地运行所有提交测试,或者让你的 CI 服务器为你做这件事

Always Run All Commit Tests Locally before Committing, or Get Your CI Server to Do It for You

正如我们已经确定的那样,提交会触发候选发布版本的创建。它是一种出版物。大多数人会在以任何形式发布作品之前检查他们的作品,签到也不例外。

As we have already established, a commit triggers the creation of a release candidate. It is a kind of publication. Most people will check their work before publishing it in any form, and a check-in is no different.

我们希望签到足够轻便,这样我们就可以乐于每隔二十分钟左右定期签到一次,但又要足够正式,这样我们在提交之前会短暂地停下来思考一下。在本地运行提交测试是提交操作之前的健全性检查。这也是一种确保我们认为有效的方法确实有效的方法。

We want check-ins to be lightweight enough so we can be happy to check in regularly every twenty minutes or so, but also formal enough so that we will briefly pause to think about it before committing. Running the commit tests locally is a sanity check before committing to the action. It is also a way to ensure that what we believe to work actually does.

当开发人员暂停并准备提交时,他们应该通过从版本控制系统更新来刷新项目的本地副本。然后他们应该启动本地构建并运行提交测试。只有当这成功时,开发人员才准备好将更改提交到版本控制系统。

As developers come to a pause and are ready to commit, they should refresh their local copy of the project by updating from the version control system. They should then initiate a local build and run the commit tests. Only when this is successful is the developer ready to commit the changes to the version control system.

如果您以前没有遇到过这种方法,您可能想知道为什么我们在签入之前在本地运行提交测试,如果签入时发生的第一件事是编译代码并重新运行提交测试。采用这种方法有两个原因:

If you haven’t encountered this approach before, you may be wondering why we run the commit tests locally before checking in, if the first thing that will happen on check-in is that the code will be compiled and the commit tests rerun. There are two reasons for this approach:

1. 其他人可能在你上次从版本控制更新之前已经签入,你的新更改和他们的新更改的组合可能会导致测试失败。如果您签出并在本地运行提交测试,您将在不破坏构建的情况下识别此问题。

1. Other people may have checked in before your last update from version control, and the combination of your new changes and theirs might cause tests to fail. If you check out and run the commit tests locally, you will identify this problem without breaking the build.

2. 签入时常见的错误来源是忘记向存储库添加一些新工件。如果你遵循这个过程,你的本地构建通过了,然后你的 CI 管理系统在提交阶段失败了,你知道这要么是因为有人在此期间签入,要么是因为你忘记添加新的类或配置文件您刚刚开始研究版本控制系统。

2. A common source of errors on check-in is to forget to add some new artifact to the repository. If you follow this procedure, and your local build passes, and then your CI management system fails the commit stage, you know that it is either because someone checked in in the meantime, or because you forgot to add the new class or configuration file that you have just been working on into the version control system.

遵循这种做法可确保构建保持绿色。

Following this practice ensures the build stays green.

许多现代 CI 服务器提供了一种称为预测试提交、个人构建或预检构建的功能。使用此工具,您的 CI 服务器将采用您的本地更改并在 CI 网格上使用它们运行构建,而不是自己签入。如果构建通过,CI 服务器将为您检查您的更改。如果构建失败,它会让您知道哪里出了问题。这是遵循这种做法的好方法,而不必等到提交测试通过才开始处理下一个功能或错误修复。

Many modern CI servers offer a feature variously known as pretested commit, personal build, or preflight build. Using this facility, instead of checking in yourself, your CI server will take your local changes and run a build with them on the CI grid. If the build passes, the CI server will check your changes in for you. If the build fails, it will let you know what went wrong. This is a great way to follow this practice without having to wait until the commit tests pass to start working on the next feature or bugfix.

在撰写本文时,CI 服务器 Pulse、TeamCity 和 ElectricCommander 都提供此功能。这种做法最好与分布式版本控制系统结合使用,该系统使您可以在本地存储提交,而无需将它们推送到中央服务器。通过这种方式,如果您的个人构建失败,可以很容易地通过创建补丁来搁置您的更改,并恢复到您发送到 CI 服务器的代码版本。

At the time of writing, the CI servers Pulse, TeamCity, and ElectricCommander all offer this feature. This practice is best combined with a distributed version control system which lets you store commits locally without pushing them to the central server. In this way, it is very easy to shelve your changes by creating a patch and revert back to the version of the code you sent to the CI server if your personal build fails.

在继续之前等待提交测试通过

Wait for Commit Tests to Pass before Moving On

CI 系统是团队的共享资源。当一个团队有效地使用 CI,遵循我们的建议并经常检查时,构建的任何破坏都是团队和整个项目的一个小绊脚石。

The CI system is a shared resource for the team. When a team is using CI effectively, following our advice and checking in frequently, any breakage of the build is a minor stumbling block for the team and project as a whole.

但是,构建中断是过程中正常且预期的部分。我们的目标是尽快发现错误并消除错误,而不期望完美和零错误。

However, build breakages are a normal and expected part of the process. Our aim is to find errors and eliminate them as quickly as possible, without expecting perfection and zero errors.

在签入时,创建它的开发人员负责监控构建的进度。在他们的签入编译并通过其提交测试之前,开发人员不应开始任何新任务。他们不应该出去吃午饭或开会。他们应该对构建给予足够的关注,以便在提交阶段完成后的几秒钟内了解其结果。

At the point of check-in, the developers who made it are responsible for monitoring the build’s progress. Until their check-in has compiled and passed its commit tests, the developers should not start any new task. They shouldn’t go out for lunch or start a meeting. They should be paying sufficient attention to the build to know its outcome within a few seconds of the commit stage completing.

如果提交成功,开发人员然后,并且只有到那时,才能自由地继续他们的下一个任务。如果失败,他们将立即开始确定问题的性质并修复它——通过另一次签入或在版本控制中恢复到以前的版本,也就是说,撤销他们的更改,直到他们了解如何进行更改工作。

If the commit succeeds, the developers are then, and only then, free to move on to their next task. If it fails, they are at hand to start determining the nature of the problem and fixing it—with another check-in or a revert to the previous version in version control, that is, backing out their changes until they understand how to make them work.

永远不要在破损的建筑上回家

Never Go Home on a Broken Build

现在是下午5:30 。星期五,你所有的同事都走出了门,而你刚刚提交了你的更改。构建已损坏。你有三个选择。您可以接受自己将迟到的事实,并尝试解决它。您可以在下周恢复您的更改并返回您的签到尝试。或者您现在可以离开,让构建损坏。

It is 5:30 P.M. on Friday, all your colleagues are walking out of the door, and you have just committed your changes. The build has broken. You have three options. You can resign yourself to the fact that you will be leaving late, and try to fix it. You can revert your changes and return to your check-in attempt next week. Or you can leave now and leave the build broken.

如果你让构建失败,当你周一返回时,你对所做更改的记忆将不再清晰,你将花费更长的时间来理解问题并修复它。如果您不是周一早上第一个回来修复构建的人,那么当团队的其他成员到达时发现构建已损坏并且他们的工作能力受到损害时,您的名字将与团队的其他成员混在一起。如果你周末病倒了,第二天又没来上班,你可能会接到好几个电话,询问你是如何搞砸构建以及如何修复它的细节,或者你的修订版本被无礼地丢弃了你的一位同事。尽管如此,你的名字将是泥泞的。

If you leave the build broken, when you return on Monday your memory of the changes you made will no longer be fresh, and it will take you significantly longer to understand the problem and fix it. If you aren’t the first person back fixing the build on Monday morning, your name will be mud with the rest of the team when they arrive to find the build broken and their ability to work compromised. If you are taken ill over the weekend, and don’t make it in to work the next day, expect either several phone calls asking for details of how you messed up the build and how to fix it, or having your revision unceremoniously dumped by one of your colleagues. Still, your name will be mud.

如果您在一个分布在不同时区的分布式开发团队中工作,那么通常来说,一个损坏的构建的影响,特别是在一天的工作结束时留下的一个损坏的构建,会被放大。在这些情况下,返回一个损坏的构建可能是疏远远程同事的最有效方法之一。

The effect of a broken build generally, and specifically a build left broken at the end of a day’s work, is magnified if you are working in a distributed development team with groups in different time zones. In these circumstances, going home on a broken build is perhaps one of the most effective ways of alienating your remote colleagues.

需要明确的是,我们不建议您在下班后熬夜修复构建相反,我们建议您定期并尽早检查,以便在出现问题时给自己时间来处理问题。或者,将您的入住记录保存到第二天;许多有经验的开发人员都强调在工作结束前不到一个小时不会签到,而是留到第二天早上做第一件事。如果一切都失败了,只需从源代码控制中恢复您的更改并将其保留在您的本地工作副本中。一些版本控制系统,包括所有分布式系统,允许您在本地存储库中累积签入,而无需将它们推送给其他用户,从而使这更容易。

Just to be absolutely clear, we are not recommending that you stay late to fix the build after working hours. Rather, we recommend that you check in regularly and early enough to give yourself time to deal with problems should they occur. Alternatively, save your check-in for the next day; many experienced developers make a point of not checking in less than an hour before the end of work, and instead leave that to do first thing the next morning. If all else fails, simply revert your change from source control and leave it in your local working copy. Some version control systems, including all the distributed ones, make this easier by allowing you to accumulate check-ins within your local repository without pushing them to other users.

随时准备恢复到以前的版本

Always Be Prepared to Revert to the Previous Revision

正如我们之前所述,虽然我们努力勤奋,但我们都会犯错误,因此我们预计每个人都会时不时地破坏构建。在较大的项目中,这通常每天都会发生,尽管预先测试的提交将大大缓解这种情况。在这些情况下,修复通常是简单的事情,我们会立即识别并通过提交一个小的单行更改来修复。然而,有时我们犯的错误比这更多,要么找不到问题所在,要么在签入失败后我们意识到我们错过了一些关于我们刚刚所做的更改的重要内容。

As we described earlier, while we try hard to be diligent, we all make mistakes, so we expect that everyone will break the build from time to time. On larger projects, it is often a daily occurrence, though pretested commits will greatly alleviate this. In these circumstances, the fixes are normally simple things that we will recognize immediately and fix by committing a small one-line change. However, sometimes we get it more wrong than that, and either can’t find where the problem lies, or just after the check-in fails we realize that we missed something important about the nature of the change that we have just made.

无论我们对失败的提交阶段有何反应,重要的是我们要让一切迅速恢复正常。如果我们不能快速解决问题,无论出于何种原因,我们都应该恢复到版本控制中保存的先前变更集,并在我们的本地环境中解决问题。毕竟,我们首先想要一个版本控制系统的原因之一就是让我们能够自由地恢复。

Whatever our reaction to a failed commit stage, it is important that we get everything working again quickly. If we can’t fix the problem quickly, for whatever reason, we should revert to the previous change-set held in revision control and remedy the problem in our local environment. After all, one of the reasons that we want a revision control system in the first place is to allow us precisely this freedom to revert.

飞机驾驶员被教导说,每次他们着陆时,他们都应该假设会出现问题,因此他们应该准备好中止着陆尝试并“复飞”以进行另一次尝试。签入时使用相同的思维方式。假设您可能会破坏一些需要几分钟以上的时间,并且知道如何恢复更改并返回到版本控制中已知的良好修订。您知道以前的修订版很好,因为您没有检查损坏的构建

Airplane pilots are taught that every time they land, they should assume that something will go wrong, so they should be ready to abort the landing attempt and “go around” to make another try. Use the same mindset when checking in. Assume that you may break something that will take more than a few minutes, and know what to do to revert the changes and get back to the known-good revision in version control. You know that the previous revision was good because you don’t check in on a broken build.

恢复前的时间盒修复

Time-Box Fixing before Reverting

建立团队规则:当构建在签入时中断,请尝试修复它十分钟。如果十分钟后,您还没有完成解决方案,请从您的版本控制系统恢复到以前的版本。有时候,如果我们觉得特别宽容,我们会给你一点回旋余地。例如,如果您正在本地构建中准备签入,我们会让您完成它以查看它是否有效。如果有效,您可以签入并希望您的修复会很好;如果它在本地或签入后失败,则恢复到最后一个已知的良好状态。

Establish a team rule: When the build breaks on check-in, try to fix it for ten minutes. If, after ten minutes, you aren’t finished with the solution, revert to the previous version from your version control system. Sometimes, if we are feeling particularly lenient, we will allow you a little leeway. If you are in the middle of your local build preparing for the check-in, for example, we will let you finish that to see if it works. If it works, you can check in and hopefully your fix will be good; if it fails either locally or following check-in, revert to the last known-good state.

有经验的开发人员通常会在任何情况下都执行此规则,愉快地恢复其他人损坏十分钟或更长时间的构建。

Experienced developers will often enforce this rule in any case, happily reverting other people’s builds that are broken for ten minutes or more.

不要注释掉失败的测试

Don’t Comment Out Failing Tests

一旦您开始执行之前的规则,结果通常是开发人员注释掉失败的测试,以便签入他们的更改。这种冲动是可以理解的,但却是错误的。当已经通过一段时间的测试开始失败时,可能很难找出原因。回归问题真的被发现了吗?也许测试的假设之一不再有效,或者应用程序确实出于正当理由更改了被测试的功能。找出这些条件中的哪些是适用的可能需要与一大群人交谈并花费时间,但必须投入工作以找出正在发生的事情并修复代码(如果发现回归) 、修改测试(如果其中一个假设已更改),或删除它(如果被测功能不再存在)。

Once you begin to enforce the previous rule, the result is often that developers comment out failing tests in order to get their changes checked in. This impulse is understandable, but wrong. When tests that have been passing for a while begin to fail, it can be hard to work out why. Has a regression problem really been found? Perhaps one of the assumptions of the test is no longer valid, or the application really has changed the functionality being tested for a valid reason. Working out which of these conditions is applicable can involve talking to a whole bunch of people and take time, but it is essential to put in the work to find out what is going on and either fix the code (if a regression has been found), modify the test (if one of the assumptions has changed), or delete it (if the functionality under test no longer exists).

注释掉失败的测试应该永远是最后的手段,很少和勉强使用,除非你有足够的纪律来立即修复它。偶尔注释掉一个测试是可以的,该测试等待一些需要安排的严肃的开发工作或与客户的一些扩展讨论。然而,这可能会让你走下坡路。我们已经看到一半测试被注释掉的代码。建议跟踪评论测试的数量并将其显示在一个大的、可见的图表或屏幕上。如果评论测试的数量超过某个阈值(可能是总数的 2%),您甚至可能会导致构建失败。

Commenting out tests that fail should always be a last resort, very rarely and reluctantly used, unless you are disciplined enough to fix it right away. It is OK to very occasionally comment out a test pending either some serious development work that needs to be scheduled or some extended discussions with the customer. However, this can push you down a slippery slope. We’ve seen code where half the tests were commented out. It’s advisable to track the number of commented tests and display it on a big, visible chart or screen. You could even fail the build if the number of commented tests exceeds some threshold, maybe 2% of the total.

对因您的更改而导致的所有损坏负责

Take Responsibility for All Breakages That Result from Your Changes

如果您提交更改并且您编写的所有测试都通过了,但其他测试失败了,那么构建仍然失败。通常这意味着您在应用程序中引入了回归错误。您有责任——因为您进行了更改——修复所有由于您的更改而未通过的测试。在 CI 的上下文中,这似乎是显而易见的,但实际上在许多项目中并不常见。

If you commit a change and all the tests you wrote pass, but others break, the build is still broken. Usually this means that you have introduced a regression bug into the application. It is your responsibility—because you made the change—to fix all tests that are not passing as a result of your changes. In the context of CI this seems obvious, but actually it is not common practice in many projects.

这种做法有几个含义。这意味着您需要访问任何可以突破您的更改的代码,以便在它出现故障时修复它。这意味着您不能让开发人员拥有只有他们才能处理的代码子集。为了有效地进行 CI,每个人都需要访问整个代码库。如果由于某些原因你被迫进入无法与整个团队共享代码访问权限的情况,你可以通过与具有必要访问权限的人员的良好协作来解决它。但是,这是次优选择,您应该努力去除此类限制。

This practice has several implications. It means that you need to have access to any code that you can break through your changes, so you can fix it if it breaks. It means that you can’t afford to have developers own a subset of the code that only they can work on. To do CI effectively, everybody needs access to the whole codebase. If for some reasons you are forced into a situation where access to code cannot be shared with the whole team, you can manage around it through good collaboration with the people who have the necessary access. However, this is very much a second-best, and you should work hard to get such restrictions removed.

测试驱动开发

Test-Driven Development

拥有全面的测试套件对于持续集成至关重要。虽然我们将在下一章详细讨论自动化测试策略,但值得强调的是,快速反馈是持续集成的核心成果,只有在出色的单元测试覆盖率下才​​有可能(出色的验收测试覆盖率也很重要,但这些测试需要更长的时间才能运行)。根据我们的经验,获得出色的单元测试覆盖率的唯一方法是通过测试驱动开发。虽然我们试图避免在本书中对敏捷开发实践教条,但我们认为测试驱动开发对于实现持续交付实践是必不可少的。

Having a comprehensive test suite is essential to continuous integration. While we deal at length with strategies for automated testing in the next chapter, it is worth highlighting that the fast feedback, which is the core outcome of continuous integration, is only possible with excellent unit test coverage (excellent acceptance test coverage is also essential, but these tests take longer to run). In our experience, the only way to get excellent unit test coverage is through test-driven development. While we have tried to avoid being dogmatic about agile development practices in this book, we think test-driven development is essential to enable the practice of continuous delivery.

对于那些不熟悉测试驱动开发的人来说,这个想法是在开发新功能或修复错误时,开发人员首先创建一个测试,该测试是要编写的代码的预期行为的可执行规范。这些测试不仅驱动应用程序的设计,而且还用作回归测试以及代码和应用程​​序预期行为的文档。

For those not familiar with test-driven development, the idea is that when developing a new piece of functionality or fixing a bug, developers first create a test that is an executable specification of the expected behavior of the code to be written. Not only do these tests drive the application’s design, they then serve both as regression tests and as documentation of the code and the application’s expected behavior.

对测试驱动开发的讨论超出了本书的范围。然而,值得注意的是,对于所有此类实践,在测试驱动开发方面保持纪律和务实是很重要的。我们有两本书推荐用于进一步阅读该主题:Steve Freeman 和 Nat Pryce 的Growing Object-Oriented Software, Guided by Tests和 Gerard Meszaros 的xUnit Test Patterns: Refactoring Test Code

A discussion of test-driven development is beyond the scope of this book. It is, however, worth noting that as with all such practices it is important to be both disciplined and pragmatic about test-driven development. We have two book recommendations for further reading on this topic: Steve Freeman and Nat Pryce’s Growing Object-Oriented Software, Guided by Tests, and Gerard Meszaros’ xUnit Test Patterns: Refactoring Test Code.

建议的做法

Suggested Practices

以下实践不是必需的,但我们发现它们很有用,您至少应该考虑将它们用于您的项目。

The following practices aren’t required, but we have found them useful, and you should at least consider using them for your project.

极限编程(XP)开发实践

Extreme Programming (XP) Development Practices

持续集成是 Kent Beck 书中描述的 12 项核心 XP 实践之一,因此它与其他 XP 实践相互补充。持续集成甚至可以对任何团队产生巨大的影响如果他们没有使用任何其他做法,但与其他做法结合使用会更有效。特别是,除了我们在上一节中描述的测试驱动开发和共享代码所有权之外,您还应该将重构视为有效软件开发的基石。

Continuous integration is one of the twelve core XP practices described in Kent Beck’s book, and as such it complements and is complemented by the other XP practices. Continuous integration can make a huge difference to any team even if they are not using any of the other practices, but it is even more effective in conjunction with the other practices. In particular, in addition to test-driven development and shared code ownership, which we described in the previous section, you should also consider refactoring as a cornerstone of effective software development.

重构意味着在不改变应用程序行为的情况下进行一系列小的增量更改来改进代码。CI 和测试驱动开发通过向您保证您的更改不会改变应用程序的现有行为来启用重构。因此,您的团队可以自由地进行可能触及大部分代码的更改,而不必担心它们会破坏应用程序。这种做法还支持频繁签入——开发人员在每次小的增量更改后签入。

Refactoring means making a series of small, incremental changes that improve your code without changing your application’s behavior. CI and test-driven development enable refactoring by assuring you that your changes don’t alter the existing behavior of the application. Thus your team becomes free to make changes which might touch large areas of the code without worrying that they can break the application. This practice also enables frequent check-ins—developers check in after each small, incremental change.

因架构漏洞而导致构建失败

Failing a Build for Architectural Breaches

有时,系统架构的某些方面很容易让开发人员忘记。我们使用的一种技术是进行一些提交时间测试,以证明没有发生违反这些规则的情况。

Sometimes there are aspects of the architecture of a system that are too easy for developers to forget. One technique that we have used is to place some commit-time tests that prove that breaches of these rules are not taking place.

这种技术实际上只是一种战术技术,除了举例之外很难描述。

This technique is really only a tactical one and difficult to describe other than by example.

这种技术看起来有点重量级,并不能替代开发团队对正在开发的系统架构的清晰理解。然而,当有重要的架构问题需要防御时,它会非常有用——否则很难及早发现的问题。

This technique can seem a little heavyweight and is not a replacement for a clear understanding of the architecture of the system under development within the development team. However, it can be very useful when there are important architectural issues to defend—things that could otherwise be difficult to catch early.

构建慢速测试失败

Failing the Build for Slow Tests

正如我们之前所说,CI 最适合小而频繁的提交。如果提交测试需要很长时间才能运行,那么由于等待构建和测试过程完成所花费的时间,它会对团队的生产力产生严重的不利影响。反过来,这会阻止频繁的签到,因此团队将开始存储他们的签到,使每个签到都变得更加复杂——合并冲突的可能性更大,引入错误的可能性更大,从而导致测试失败。所有这些都进一步减慢了一切。

As we have said before, CI works best with small, frequent commits. If the commit tests take a long time to run, it can have a seriously detrimental effect on the productivity of the team because of the time spent waiting for the build and test process to complete. This will, in turn, discourage frequent check-ins, so the team will start to store up their check-ins, making each one more complex—with more likelihood of merge conflicts and more chance of introducing errors, and so failing the tests. All this slows everything down even further.

为了让开发团队专注于保持测试快速的重要性,如果您发现单个测试花费的时间超过某个指定时间,则可以使提交测试失败。上次我们使用这种方法时,对于运行时间超过两秒的任何测试,我们的构建都失败了。

To keep the development team focused on the importance of keeping the tests fast, you can fail the commit tests if you find an individual test that takes longer than some specified time. Last time we used this approach we failed the build for any test that took more than two seconds to run.

我们倾向于喜欢小的改变可以产生更广泛影响的做法。这就是这样一种做法。如果开发人员编写的提交测试运行时间过长,则当他们准备好提交更改时,构建将失败。这鼓励他们仔细考虑使测试快速运行的策略。如果测试运行得很快,开发人员将更频繁地签入。如果开发人员更频繁地签入,出现合并问题的可能性就会降低,并且出现的任何问题都可能很小并且可以快速解决,因此开发人员的工作效率更高。

We tend to like practices where a small change can have a wider effect. This is just such a practice. If a developer writes a commit test that takes too long to run, the build will fail when they get ready to commit their change. This encourages them to think carefully about strategies to make their tests run quickly. If the tests run quickly, developers will check in more frequently. If the developers check in more frequently, there is less chance of merge problems, and any problem that does arise is likely to be small and quick to solve, so developers are more productive.

但有一个警告:这种做法可能有点像一把两刃剑。如果您的 CI 环境由于某种原因处于异常负载下,您需要小心创建会失败的不稳定的间歇性测试。我们发现使用这种方法最有效的方法是作为一种让大型团队专注于特定问题的策略,而不是我们在每次构建中都会采用的方法。如果您的构建速度变慢,您可以使用这种方法让团队在一段时间内专注于加快速度。

There is a caveat though: This practice can be a bit of a two-edged sword. You need to be wary of creating flaky intermittent tests that fail if your CI environment is, for some reason, under unusual load. We have found that the most effective way to use this approach is as a strategy to get a large team focused on a specific problem, not as something we would employ in every build. If your build becomes slow, you can use this approach to keep the team focused, for a while, on speeding things up.

请注意:我们在这里谈论的是测试性能,而不是性能测试。容量测试在第 9 章“测试非功能性需求”中介绍。

Please note: We are talking about test performance, not performance testing here. Capacity testing is covered in Chapter 9, “Testing Nonfunctional Requirements.”

因警告和代码风格违规而导致构建失败

Failing the Build for Warnings and Code Style Breaches

编译器警告通常是出于充分的理由警告您。尽管我们的开发团队通常将其称为“代码纳粹”,但我们已成功采用的一种策略是在警告时使构建失败。这可以是在某些情况下有点苛刻,但作为一种强制执行良好做法的方式,它是有效的。

Compiler warnings are usually warning you for good reasons. A strategy that we have adopted with some success, though it is often referred to as the “code Nazi” by our development teams, is to fail the build on warnings. This can be a bit draconian in some circumstances, but as a way to enforce good practice it is effective.

您可以通过添加对特定或一般编码错误的检查来尽可能多地加强此技术。我们使用了众多开源代码质量工具之一并取得了一些成功:

You can strengthen this technique as much as you wish by adding checks for specific or general coding lapses. We have used one of the many open source code-quality tools with some success:

• Simian 是一种识别大多数流行语言(包括纯文本)中重复项的工具。

• Simian is a tool that identifies duplication in most popular languages (including plain text).

• JDepend for Java 及其商业 .NET 表亲 NDepend,生成大量有用(和一些不太有用)的设计质量指标。

• JDepend for Java, and its commercial .NET cousin NDepend, generate a wealth of useful (and some less useful) design quality metrics.

• CheckStyle 可以测试不良的编码实践,例如实用程序类中的公共构造函数、嵌套块和长行。它还可以捕获错误和安全漏洞的常见来源。它可以很容易地扩展。FxCop 是它的 .NET 表亲。

• CheckStyle can test for bad coding practices, such as public constructors in utility classes, nested blocks, and long lines. It can also catch common sources of bugs and security holes. It can easily be extended. FxCop is its .NET cousin.

• FindBugs 是一个基于 Java 的系统,提供了 CheckStyle 的替代方案,包括一组类似的验证。

• FindBugs is a Java-based system providing an alternative to CheckStyle, including a similar set of validations.

正如我们已经说过的,对于一些在任何警告下构建失败的项目来说,听起来可能过于严厉。我们用来逐渐引入这种做法的一种方法是棘轮这意味着将警告或 TODO 等事项的数量与上一次签到中的数量进行比较。如果数量增加,则构建失败。使用这种方法,您可以轻松地执行一项策略,即每次提交都应至少将警告或 TODO 的数量减少一个。

As we have said, for some projects failing the build on any warning may sound too draconian. One approach that we have used to introduce this practice gradually is ratcheting. This means comparing the number of things like warnings or TODOs with the number in the previous check-in. If the number increases, we fail the build. Using this approach, you can easily enforce a policy that every commit should reduce the number of warnings or TODOs at least by one.

分布式团队

Distributed Teams

就流程和技术而言,与分布式团队一起使用持续集成与在任何其他环境中基本相同。然而,团队没有坐在同一个房间里这一事实——也许他们甚至在不同的时区工作——确实对其他一些领域产生了影响。

Using continuous integration with distributed teams is, in terms of process and technology, largely the same as in any other environment. However, the fact that the team is not sitting together in the same room—perhaps they are even working in different time zones—does have an impact in some other areas.

从技术角度最简单,从流程角度最有效的方法,就是保留一个共享的版本控制系统和持续集成系统。如果您的项目使用后面章节中描述的部署管道,那么这些也应该简单地在平等的基础上提供给团队的所有成员。

The simplest approach from a technical perspective, and the most effective from a process perspective, is to retain a shared version control system and continuous integration system. If your project uses deployment pipelines as described in later chapters, these too should be simply made available on an equal basis to all members of the team.

当我们说这种方法最有效时,我们应该强调它非常有效。为实现这个理想而努力是值得的;此处描述的所有其他方法都以显着优势仅次于此。

When we say that this approach is the most effective, we should emphasize that it is very considerably so. It is worth working hard to achieve this ideal; all other approaches described here are second-best to this by a significant margin.

对过程的影响

The Impact on Process

对于同一时区内的分布式团队,持续集成大同小异。你当然不能使用物理签入令牌——尽管一些 CI 服务器支持虚拟签入令牌——而且它更没有人情味,所以当你提醒某人修复构建时更容易引起冒犯。个人构建等功能变得更加有用。但是,总的来说,过程是相同的。

For distributed teams within the same time zone, continuous integration is much the same. You can’t use physical check-in tokens of course—although some CI servers support virtual ones—and it is a little more impersonal, so a little easier to cause offense when you remind someone to fix the build. Features such as personal builds become more useful. On the whole, however, the process is the same.

对于分布在不同时区的团队来说,需要处理的问题更多。如果旧金山的团队破坏构建并回家,这对于刚刚开始工作的北京团队来说可能是一个严重的障碍,因为旧金山团队正在离开。流程没有改变,但坚持的重要性被放大了。

For distributed teams in different time zones, there are more issues to deal with. If the team in San Francisco breaks the build and goes home, this can be a serious handicap for the team in Beijing who are just starting work as the San Francisco team are leaving. The process does not change, but the importance of adhering to it is magnified.

在具有分布式团队的大型项目中,诸如 VoIP(例如 Skype)和即时消息之类的工具对于实现保持事情顺利进行所必需的细粒度通信非常重要。与开发相关的每个人——项目经理、分析师、开发人员、测试人员——都应该能够访问 IM 和 VoIP 上的其他所有人,并且可以访问这些人。定期来回飞行对交付过程的顺利进行至关重要,这样每个本地组都可以与其他组的成员进行个人接触。这对于在团队成员之间建立信任很重要——这通常是分布式团队中最先受到影响的事情。可以使用视频会议进行回顾、展示、站立和其他定期会议。另一个很棒的技巧是让每个开发团队录制一段短片,

In large projects with distributed teams, tools like VoIP (e.g., Skype) and instant messaging are of enormous importance to enable the fine-grained communications necessary to keep things running smoothly. Everyone associated with development—project managers, analysts, developers, testers—should have access to, and be accessible to, everyone else on IM and VoIP. It is essential for the smooth running of the delivery process to fly people back and forth periodically, so that each local group has personal contact with members from other groups. This is important to build up trust between team members—often the first thing to suffer in a distributed team. It is possible to do retrospectives, showcases, stand-ups, and other regular meetings using videoconferencing. Another great technique is to have each development team record a short video, using screen capture software, that talks through the functionality they’ve been working on that day.

当然,这是一个比持续集成更广泛的话题。我们打算表达的观点只是保持流程不变,但在其应用中更加严格。

Naturally, this is a much wider topic than just continuous integration. The point we intend to make is simply to keep the process the same, but be even more disciplined in its application.

集中持续集成

Centralized Continuous Integration

一些更强大的持续集成服务器具有集中管理的构建农场和复杂的授权方案等设施,使您可以将持续集成作为集中服务提供给大型和分布式团队。这些系统使团队可以轻松地进行自助式持续集成,而无需获得自己的硬件。它们还允许运营团队整合服务器资源,控制持续集成和测试环境的配置以确保它们与生产环境一致且相似,并实施良好实践,例如管理第三方库的配置和提供预装工具以收集数据一致的代码覆盖率和质量指标。最后,它们允许跨项目收集和监控标准指标,

Some more powerful continuous integration servers have facilities such as centrally managed build farms and sophisticated authorization schemes that allow you to provide continuous integration as a centralized service to large and distributed teams. These systems make it easy for teams to self-service continuous integration without having to obtain their own hardware. They also allow operations teams to consolidate server resources, control the configuration of continuous integration and testing environments to ensure that they are all consistent and similar to production, and enforce good practices such as managing configuration of third-party libraries and providing preinstalled tools for gathering consistent metrics of code coverage and quality. Finally, they allow standard metrics to be gathered and monitored across projects, providing managers and delivery teams with the ability to create dashboards to monitor code quality at a program level.

虚拟化还可以与集中式 CI 服务结合使用,只需按一下按钮,就可以从存储的基线映像中启动新的虚拟机。您可以使用虚拟化使供应新环境成为一个完全自动化的过程,交付团队可以自助服务。它还确保构建和部署始终在这些环境的一致基线版本上运行。这具有消除作为“艺术品”的持续集成环境的令人愉快的效果,这些环境在数月内积累了与测试和生产环境中存在的内容无关的软件、库和配置设置。

Virtualization can also work well in conjunction with centralized CI services, providing the ability to spin up new virtual machines from stored baseline images at the press of a button. You can use virtualization to make provisioning new environments a completely automated process, which can be self-serviced by delivery teams. It also ensures that builds and deployments always run on a consistent, baseline version of these environments. This has the happy effect of removing continuous integration environments that are “works of art,” having accumulated software, libraries, and configuration settings over many months that bear no relation to what is present in testing and production environments.

集中的持续集成可以是一个双赢的局面。然而,为了做到这一点,开发团队必须能够以自动化方式轻松地为新环境、配置、构建和部署提供自助服务。如果一个团队必须发送几封电子邮件并等待数天才能为其最新发布的分支获得新的 CI 环境,他们将颠覆流程并返回使用他们办公桌下的备用箱子来进行真正的持续集成——或者更糟的是,不根本不做持续集成。

Centralized continuous integration can be a win-win situation. However, in order for this to be the case, it is essential that development teams can easily self-service new environments, configurations, builds, and deployments in an automated fashion. If a team has to send several emails and wait days to get a new CI environment for their latest release branch, they will subvert the process and go back to using spare boxes under their desks to do their real continuous integration—or, worse, not do continuous integration at all.

技术问题

Technical Issues

根据版本控制系统的选择,当团队之间的链接速度较慢时,共享对版本控制系统的访问以及为全球分布的团队构建和测试资源可能会非常痛苦。

Depending on the choice of a version control system, it can be quite painful to share access to version control systems and build and test resources for a globally distributed team when there are slow links between the teams.

当持续集成运行良好时,整个团队都会定期提交更改。这意味着与版本控制系统的交互往往会保持在一个合理的高水平。尽管就交换的字节而言,每次交互通常相对较小,但由于提交和更新的频率,沟通不畅会严重拖累生产力。值得投资足够高的带宽通信开发中心之间。还值得考虑迁移到 Git 或 Mercurial 等分布式版本控制系统,即使没有链接到通常指定的“主”服务器,人们也可以签入。

When continuous integration is working well, the whole team is committing changes regularly. This means that interaction with the version control system tends to be maintained at a reasonably high level. Although each interaction is usually relatively small in terms of bytes exchanged, because of the frequency of commits and updates, poor communication becomes a significant drag on productivity. It is worth investing in sufficiently high-bandwidth communications between development centers. It is also worth considering to move to a distributed version control system such as Git or Mercurial that allows people to check in even when there is no link to the conventionally designated “master” server.

版本控制系统合理地接近承载自动化测试运行的构建基础设施是有意义的。如果这些测试在每次签入后运行,则意味着网络中的系统之间有相当多的交互。

It makes sense for the version control system to be reasonably close to the build infrastructure that hosts the running of automated tests. If these tests are being run after every check-in, that implies a fair amount of interaction between the systems across the network.

托管版本控制系统、持续集成系统和部署管道中的各种测试环境的物理机器需要能够从每个开发站点平等地访问。如果印度的版本控制系统因磁盘已满而停止工作,伦敦的开发团队将处于相当不利的地位,印度办公室的每个人都已经回家过夜,他们无法访问系统. 从每个位置提供对所有这些系统的系统管理员级别的访问权限。确保每个站点的团队不仅可以访问而且还可以管理他们轮班时可能出现的任何问题的知识。

The physical machines that host the version control system, the continuous integration system, and the various test environments in your deployment pipeline need to be accessible on an equal basis from every development site. The development team in London is going to be at a considerable disadvantage if the version control system in India stops working because the disk is full, everyone in the Indian office has gone home for the evening, and they don’t have access to the system. Provide sysadmin-level access to all of these systems from every location. Ensure that the teams at each site not only have access but also the knowledge to manage any problems that may occur on their shift.

替代方法

Alternative Approaches

如果有一些无法克服的问题阻止花费更多的钱在你的开发中心之间建立更高带宽的通信,那么有可能,但不理想,有本地持续集成和测试系统,甚至在极端情况下本地版本控制系统情况。如您所料,我们真的不建议采用这种方法。尽你所能避免它;它在时间和精力方面都很昂贵,而且效果不如共享访问。

If there is some insurmountable problem that prevents spending a little more to get higher-bandwidth communications established between your development centers, then it is possible, but not ideal, to have local continuous integration and test systems, and even local version control systems in extreme circumstances. As you might expect, we really don’t advise this approach. Do everything you can to avoid it; it is expensive in terms of time and effort and doesn’t work nearly as well as shared access.

简单的东西是持续集成系统。很有可能拥有本地持续集成服务器和测试环境,甚至是成熟的本地部署管道。当现场进行大量手动测试时,这可能很有价值。当然,这些环境需要谨慎管理,以确保它们在各地区保持一致。唯一需要注意的是,理想情况下,二进制文件或安装程序应该只构建一次,然后运送到需要它们的全球所有位置。然而,由于大多数安装程序的庞大规模,这通常是不切实际的。如果您必须在本地构建二进制文件或安装程序,那么确保严格管理工具链的配置以确保在任何地方创建完全相同的二进制文件就变得更加重要。强制执行此操作的一种方法是使用 md5 或类似算法自动生成二进制文件的哈希值,并让您的 CI 服务器根据“主”二进制文件的哈希值自动检查它们以确保没有差异。

The easy stuff is the continuous integration system. It is quite possible to have local continuous integration servers and test environments, even a full-blown local deployment pipeline. This can be of value when there is a significant amount of manual testing being undertaken at a site. Of course, these environments need to be managed carefully to ensure they are consistent across regions. The only caveat is that ideally, binaries or installers should only be built once, and then shipped to all global locations where they are required. However, this is often impractical due to the sheer size of most installers. If you have to build binaries or installers locally, it becomes even more essential to ensure that you manage the configuration of your toolchain rigorously to ensure exactly the same binaries are created everywhere. One approach to enforce this is to automatically generate hashes of your binaries, using md5 or a similar algorithm, and have your CI server automatically check them against the hashes of the “master” binaries to ensure there are no differences.

在某些极端情况下,例如,如果版本控制系统是远程的并且通过缓慢或不可靠的链接连接,则在本地托管持续集成系统的价值会受到严重损害。我们在使用持续集成时常说的目标是尽早发现问题的能力。如果版本控制系统以任何方式分裂,我们就会损害这种能力。在我们被迫这样做的情况下,我们拆分版本控制系统的目标必须是最小化引入错误和我们能够发现错误之间的时间。

In certain extreme situations, for example if the version control system is remote and connected via a slow or unreliable link, the value of hosting the continuous integration system locally is seriously compromised. Our oft-stated objective in the use of continuous integration is the ability to identify problems at the earliest opportunity. If the version control system is split, in any manner, we compromise this ability. In circumstances where we are forced to do so, our goal in splitting the version control system must be to minimize the time between an error being introduced and our being able to spot it.

主要有两种选择可以为分布式团队提供对版本控制系统的本地访问:将应用程序划分为组件以及使用分布式或支持多主机拓扑的版本控制系统。

Primarily, there are two options for providing local access to version control systems for distributed teams: division of the application into components and the use of version control systems that are either distributed or support multimaster topologies.

在基于组件的方法中,版本控制存储库和团队都按组件或功能边界划分。第 13 章“管理组件和依赖项”中更详细地讨论了这种方法。

In the component-based approach, both the version control repositories and the teams are divided either by component or by functional boundary. This approach is discussed in much more detail in Chapter 13, “Managing Components and Dependencies.”

我们看到的另一种技术是拥有团队本地存储库并使用共享的全局主存储库构建系统。功能分离的团队在整个工作日致力于他们的本地存储库。在每天的固定时间,通常在另一个时区的一个分布式团队完成一天的工作后,本地团队的一名成员负责为整个团队提交所有更改,并承担合并一个整个变化的集合。显然,如果您使用专为此类任务设计的分布式版本控制系统,这会容易得多。然而,这个解决方案绝不是理想的,而且由于引入了重大的合并冲突,我们已经看到它惨遭失败。

Another technique that we have seen is to have team-local repositories and build systems with a shared global master repository. The functionally separated teams commit to their local repositories throughout the working day. At a regular time each day, usually after one of the distributed teams in another time zone have finished work for the day, one member of the local team takes responsibility to commit all of the changes for the entire team and takes the pain of merging a whole collection of changes. Clearly, this is much easier if you’re using a distributed version control system which is designed for exactly this sort of task. However, this solution is by no means ideal, and we have seen it fail miserably, due to the introduction of significant merge conflicts.

总之,我们在本书中描述的所有技术都在许多项目的分布式团队中得到了很好的证明。事实上,我们会将 CI 的使用视为影响地理分布的团队有效协作能力的两个或三个最重要因素之一。持续集成的持续部分很重要;如果真的没有其他选择,也有一些变通办法,但我们的建议是把钱花在通信带宽上——从中长期来看,它更便宜。

In summary, all of the techniques that we describe in this book have been well proven in distributed teams on many projects. In fact, we would view the use of CI as one of the two or three most important factors in the ability of geographically distributed teams to work effectively together. The continuous part of continuous integration is important; if there really are no other options, there are some workarounds, but our advice is to spend the money on communications bandwidth instead—in the medium and long term, it is cheaper.

分布式版本控制系统

Distributed Version Control Systems

分布式版本控制系统 (DVCS) 的兴起正在彻底改变团队合作的方式。在开源项目曾经通过电子邮件发送补丁或在论坛上发布补丁的地方,Git 和 Mercurial 等工具使得在开发人员和团队之间来回拉补丁以及分支和合并工作流变得异常容易。DVCS 允许您轻松离线工作,在本地提交更改,并在将它们推送给其他用户之前重新设置或搁置它们。DVCS 的核心特征是每个存储库都包含项目的整个历史,这意味着除非约定,否则没有存储库具有特权。因此,与集中式系统相比,DVCS 有一个额外的间接层:对本地工作副本的更改必须先签入本地存储库,然后才能推送到其他存储库,

The rise of distributed version control systems (DVCSs) is revolutionizing the way teams cooperate. Where open source projects once emailed patches or posted them on forums, tools like Git and Mercurial make it incredibly easy to pull patches back and forth between developers and teams and to branch and merge work streams. DVCSs allow you to work easily offline, commit changes locally, and rebase or shelve them before pushing them to other users. The core characteristic of a DVCS is that every repository contains the entire history of the project, which means that no repository is privileged except by convention. Thus, compared to centralized systems, DVCSs have an additional layer of indirection: Changes to your local working copy must be checked in to your local repository before they can be pushed to other repositories, and updates from other repositories must be reconciled with your local repository before you can update your working copy.

DVCS 提供了新的强大的协作方式。例如,GitHub 开创了一种新的开源项目协作模式。在传统模型中,提交者充当项目最终存储库的看门人,接受或拒绝来自贡献者的补丁。只有在提交者之间存在不可调和的争论的极端情况下,项目才会出现分叉。在 GitHub 模型中,这是完全不同的。贡献是通过首先分叉您希望贡献的项目的存储库,进行更改,然后要求原始存储库的所有者拉取您的更改来做出的。在活跃的项目中,分叉网络迅速激增,每个分叉网络都具有各种新功能。有时这些分叉会发生分歧。该模型比传统模型更具动态性,在传统模型中,补丁在邮件列表存档中萎靡不振、被忽略。因此,GitHub 上的开发速度往往更快,贡献者云也更大。

DVCSs offer new and powerful ways to collaborate. GitHub, for example, pioneered a new model of collaboration for open source projects. In the traditional model, committers acted as gatekeepers to the definitive repository for a project, accepting or rejecting patches from contributors. Forks of a project only occurred in extreme circumstances when there were irreconcilable arguments between committers. In the GitHub model, this is turned on its head. Contributions are made by first forking the repository of the project you wish to contribute to, making your changes, and then asking the owners of the original repository to pull your changes. On active projects, networks of forks rapidly proliferate, each with various new sets of features. Occasionally these forks diverge. This model is far more dynamic than the traditional model in which patches languish, ignored, on mailing list archives. As a result, the pace of development tends to be faster on GitHub, with a larger cloud of contributors.

然而,这个模型挑战了 CI 实践的一个基本假设:所有更改都提交到一个单一的规范版本的代码(通常称为主线或主干)。需要指出的是,你可以使用版本控制的主线模型,使用一个 DVCS 完美地愉快地做 CI。您只需指定一个存储库作为主存储库,每当对该存储库进行更改时让您的 CI 服务器触发,并让每个人将他们的所有更改推送到该存储库以便共享它们。这是一个非常合理的方法,我们已经在许多项目中看到它的成功使用。它保留了 DVCS 的许多优点,例如能够非常频繁地提交您的更改而不共享它们(例如保存您的游戏),这在探索新想法或执行一系列复杂的重构时非常有用。但是,有一些 DVCS 的使用模式可以防止 CI。例如,GitHub 模型违反了代码共享的主线/主干模型,因此阻止了真正的持续集成。

However, this model challenges a fundamental assumption of the practice of CI: That there is a single, canonical version of code (usually called mainline, or trunk) to which all changes are committed. It is important to point out that you can use the mainline model of version control, and do CI perfectly happily, using a DVCS. You simply designate one repository as the master, have your CI server trigger whenever a change is made to that repository, and have everybody push all their changes to this repository in order to share them. This is a perfectly reasonable approach that we have seen used successfully on many projects. It retains the many benefits of DVCS, such as the ability to commit your changes very frequently without sharing them (like saving your game), which comes in very useful while exploring a new idea or performing a complex series of refactorings. However, there are some patterns of use of DVCS that prevent CI. The GitHub model, for example, violates the mainline/trunk model of code sharing, and so prevents true continuous integration.

在 GitHub 中,每个用户的更改集都存在于一个单独的存储库中,并且无法轻松确定哪些用户将从哪些集中成功整合。您可以采用创建存储库的方法来监视所有其他存储库,并在检测到任何存储库发生更改时尝试将它们全部合并在一起。然而,这几乎总是会在合并阶段失败,更不用说在运行自动化测试时了。随着贡献者和存储库数量的增长,问题会呈指数级恶化。没有人会注意到 CI 服务器说了什么,因此 CI 作为一种传达应用程序当前是否正常工作(以及如果没有,谁和什么破坏了它)的方法失败了。

In GitHub, each user’s set of changes exists in a separate repository, and there is no way to easily determine which sets from which users will successfully integrate. You could take the approach of creating a repository to watch all the other repositories and attempt to merge them all together whenever it detects a change to any of them. However, this will almost always fail at the merge stage, let alone when running the automated tests. As the number of contributors, and hence repositories, grows, the problem gets exponentially worse. Nobody will take any notice of what the CI server says, so CI as a method of communicating whether the application is currently working (and if not, who and what broke it) fails.

图 3.2 集成分支

Figure 3.2 Integrating branches

图片

可以回退到提供持续集成的一些好处的更简单的模型。在此模型中,您为每个存储库创建一个 CI 构建。每次进行更改时,您都会尝试从指定的主存储库合并并运行构建。图 3.2显示了 CruiseControl.rb 构建 Rapidsms 项目的主存储库以及它的两个分支。

It is possible to fall back to a simpler model that provides some of the benefits of continuous integration. In this model, you create a CI build for each repository. Every time a change is made, you attempt to merge from the designated master repository and run the build. Figure 3.2 shows CruiseControl.rb building the main repository for the Rapidsms project along with two forks of it.

为了创建这个系统,使用命令git remote add core git://github.com/rapidsms/rapidsms.git 将一个指向主项目存储库的分支添加到每个 CC.rb 的 Git 存储库。每次触发构建时,CC.rb 都会尝试合并并运行构建:

In order to create this system, a branch pointing to the main project repository was added to each of CC.rb’s Git repositories using the command git remote add core git://github.com/rapidsms/rapidsms.git. Every time the build is triggered, CC.rb attempts to merge and run the build:

git fetch core

git merge --no-commit core/master

[运行构建的命令]

git fetch core

git merge --no-commit core/master

[command to run the build]

构建完成后,CC.rb 运行git reset --hard将本地存储库重置为它指向的存储库的头部。该系统不提供真正的持续集成。然而,它确实告诉分叉的维护者——以及主存储库的维护者——他们的分叉原则上是否可以是与主存储库合并,以及结果是否是应用程序的工作版本。有趣的是,图 3.2显示主存储库的构建当前已损坏,但 Dimagi 分支不仅成功地与其合并,而且还修复了损坏的测试(并且可能添加了它自己的一些附加功能)。

After the build, CC.rb runs git reset --hard to reset the local repository to head of the repository it is pointing at. This system does not provide true continuous integration. However, it does tell the maintainers of the forks—and the maintainer of the main repository—whether their fork could in principle be merged with the main repository, and whether the result would be a working version of the application. Interestingly, Figure 3.2 shows that the main repository’s build is currently broken, but the Dimagi fork not only merges successfully with it, but also fixes the broken tests (and possibly adds some additional functionality of its own).

Martin Fowler 所说的“混杂集成”[bBjxbS] 距离持续集成还差一步。在这个模型中,贡献者不仅在分支和中央存储库之间,而且在分支之间拉取更改。这种模式在使用 GitHub 的大型项目中很常见,当一些开发人员正在处理有效的长期特性分支时,并从从特性分支分支出来的其他存储库中提取更改。事实上,在这一模型中甚至不需要一个特权存储库。软件的特定版本可以来自任何分支,前提是它通过了所有测试并被项目负责人接受。该模型将 DVCS 的可能性得出了合乎逻辑的结论。

At one more step away from continuous integration is what Martin Fowler calls “promiscuous integration” [bBjxbS]. In this model, contributors pull changes not just between forks and the central repository, but also between forks. This pattern is common in larger projects that use GitHub, when some developers are working on what are effectively long-lived feature branches and pull changes from other repositories that are forked off the feature branch. Indeed in this model there need not even be one privileged repository. A particular release of the software could come from any of the forks, provided it passed all the tests and was accepted by the project leaders. This model takes the possibilities of DVCS to their logical conclusion.

这些持续集成的替代方案可以创建高质量的工作软件。但是,这仅在以下条件下才有可能:

These alternatives to continuous integration can create high-quality, working software. However, this is only possible under the following conditions:

• 一个由经验丰富的提交者组成的小型团队,负责管理拉取补丁、管理自动化测试并确保软件质量。

• A small and very experienced team of committers who manage pulling patches, tend the automated tests, and ensure the quality of the software.

• 定期从叉子中拉出,以避免大量难以合并的库存堆积在叉子上。如果有一个严格的发布时间表,这个条件就特别重要,因为很容易让合并一直持续到临近发布,这时它会变得非常痛苦——这正是持续集成旨在解决的问题。

• Regular pulling from forks, so as to avoid large amounts of hard-to-merge inventory accumulating on them. This condition is especially important if there is a strict release schedule, because the temptation is to leave merging till near the release, at which point it becomes extremely painful—the exact problem that continuous integration is designed to solve.

• 一组相对较小的核心开发人员,可能由贡献速度相对较慢的较大社区作为补充。这就是使合并易于处理的原因。

• A relatively small set of core developers, perhaps supplemented by a larger community which contributes at a relatively slow pace. This is what makes the merges tractable.

这些条件适用于大多数开源项目,也适用于一般的小型团队。但是,它们很少适用于中型或大型全职开发人员团队。

These conditions hold for most open source projects, and for small teams in general. However, they very rarely hold for medium or large teams of full-time developers.

总结一下:总的来说,分布式版本控制系统是一个巨大的进步,并提供了强大的协作工具,无论您是否正在处理分布式项目。DVCS 作为传统持续集成系统的一部分可以非常有效,其中有一个指定的中央存储库,每个人都定期将他们的更改推送到该存储库(至少每天一次)。它们也可以用于其他不允许持续集成的模式,但仍可能是交付软件的有效模式。但是,我们告诫不要在不满足上面列出的正确条件时使用这些模式。第 14 章“高级版本控制”包含对这些模式和其他模式以及它们有效的条件的完整讨论。

To summarize: In general, distributed version control systems are a great advance and provide powerful tools for collaboration, whether or not you are working on a distributed project. DVCSs can be extremely effective as part of a traditional continuous integration system, in which there is a designated central repository to which everybody regularly pushes their changes (at least once a day). They can also be used in other patterns that do not allow for continuous integration, but may still be effective patterns for delivering software. However, we caution against using these patterns when the right conditions, listed above, are not satisfied. Chapter 14, “Advanced Version Control,” contains a full discussion of these and other patterns and the conditions under which they are effective.

概括

Summary

如果你只想选择本书中的一种实践来在开发团队中实施,我们建议你选择持续集成。我们一次又一次地看到它对软件开发团队的生产力产生了巨大的影响。

If you were to choose just one of the practices in this book to implement on a development team, we would suggest that you choose continuous integration. Time and time again we have seen it make a step change to the productivity of software development teams.

实施持续集成就是在您的团队中创造范式转变。如果没有 CI,您的应用程序将被破坏,直到您证明并非如此。使用 CI,您的应用程序的默认状态是有效的,尽管其置信度取决于您的自动化测试覆盖范围。CI 创建了一个紧密的反馈循环,使您可以在问题出现后立即发现问题,而这些问题的修复成本很低。

To implement continuous integration is to create a paradigm shift in your team. Without CI, your application is broken until you prove otherwise. With CI, the default state of your application is working, albeit with a level of confidence that depends upon the extent of your automated test coverage. CI creates a tight feedback loop which allows you to find problems as soon as they are introduced, when they are cheap to fix.

实施 CI 迫使您遵循另外两个重要实践:良好的配置管理以及自动构建和测试流程的创建和维护。对于一些团队来说,这似乎需要付出很多努力,但它们可以逐步实现。我们在前一章讨论了良好配置管理的步骤。第 6 章“构建和部署脚本”中有更多关于构建自动化的内容。我们将在下一章更详细地介绍测试。

Implementing CI forces you to follow two other important practices: good configuration management and the creation and maintenance of an automated build and test process. For some teams, that will seem like a lot to bite off, but they can be achieved incrementally. We discussed the steps to good configuration management in the previous chapter. There is more on build automation in Chapter 6, “Build and Deployment Scripting.” We cover testing in more detail in the next chapter.

很明显,CI 需要良好的团队纪律——但是,任何流程都需要这样做。持续集成的不同之处在于,您可以通过一个简单的指标来判断是否遵守纪律:构建保持绿色。如果您发现构建是绿色的但没有足够的纪律,例如单元测试覆盖率低,您可以轻松地向 CI 系统添加检查以强制执行更好的行为。

It should be clear that CI requires good team discipline—but then, any process requires this. What is different about continuous integration is that you have a simple indicator of whether or not discipline is being followed: The build stays green. If you discover that the build is green but there is insufficient discipline, for example poor unit test coverage, you can easily add checks to your CI system to enforce better behavior.

这将我们带到了最后一点。已建立的 CI 系统是您可以构建更多基础设施的基础:

This brings us to our final point. An established CI system is a foundation on which you can build more infrastructure:

• 可视化大显示屏,汇总来自构建系统的信息以提供高质量反馈

• Big visible displays which aggregate information from your build system to provide high-quality feedback

• 测试团队的报告和安装程序参考系统

• A system of reference for reports and installers for your testing team

• 为项目经理提供有关应用程序质量的数据

• A provider of data on the quality of the application for project managers

• 一个可以扩展到生产环境的系统,使用部署管道,为测试人员和操作人员提供按钮式部署

• A system that can be extended out to production, using the deployment pipeline, which provides testers and operations staff with push-button deployments

第 4 章实施测试策略

Chapter 4. Implementing a Testing Strategy

介绍

Introduction

太多的项目完全依赖手动验收测试来验证软件是否符合其功能和非功能需求。即使存在自动化测试,它们也往往维护不善且已过时,需要大量手动测试作为补充。本章和本书第二部分的相关章节旨在帮助您规划和实施有效的自动化测试系统。我们提供了在常见情况下进行自动化测试的策略,并描述了支持和启用自动化测试的实践。

Too many projects rely solely on manual acceptance testing to verify that a piece of software conforms to its functional and nonfunctional requirements. Even where automated tests exist, they are often poorly maintained and out-of-date and require supplementing with extensive manual testing. This and the related chapters in Part II of this book aim to help you to plan and implement effective automated testing systems. We provide strategies for automating tests in commonly occurring situations and describe practices that support and enable automated testing.

W. Edwards Deming 的十四点之一是,“停止依赖大量检查来实现质量。首先改进流程并将质量融入产品”[9YhQXz]。测试是涉及整个团队的跨职能活动,应该从项目开始就持续进行。构建质量意味着在多个级别(单元、组件和验收)编写自动化测试并将它们作为部署管道的一部分运行,每次对应用程序、其配置或环境和软件堆栈进行更改时都会触发部署管道它运行。手动测试也是构建质量的重要组成部分: 展示、可用性测试和探索性测试需要在整个项目中持续进行。

One of W. Edwards Deming’s fourteen points is, “Cease dependence on mass inspection to achieve quality. Improve the process and build quality into the product in the first place” [9YhQXz]. Testing is a cross-functional activity that involves the whole team, and should be done continuously from the beginning of the project. Building quality in means writing automated tests at multiple levels (unit, component, and acceptance) and running them as part of the deployment pipeline, which is triggered every time a change is made to your application, its configuration, or the environment and software stack that it runs on. Manual testing is also an essential part of building quality in: Showcases, usability testing, and exploratory testing need to be done continuously throughout the project. Building quality in also means constantly working to improve your automated testing strategy.

在我们理想的项目中,测试人员与开发人员和用户协作,从项目一开始就编写自动化测试。这些测试是在开发人员开始处理他们测试的功能之前编写的。这些测试共同构成了系统行为的可执行规范,当它们通过时,就证明客户所需的功能已完全正确地实现。每次对应用程序进行更改时,CI 系统都会运行自动化测试套件——这意味着该套件还用作一组回归测试。

In our ideal project, testers collaborate with developers and users to write automated tests from the start of the project. These tests are written before developers start work on the features that they test. Together, these tests form an executable specification of the behavior of the system, and when they pass, they demonstrate that the functionality required by the customer has been implemented completely and correctly. The automated test suite is run by the CI system every time a change is made to the application—which means the suite also serves as a set of regression tests.

这些测试不仅仅测试系统的功能方面。容量、安全性和其他非功能性需求在早期就已确定,并且编写了自动化测试套件来执行它们。这些自动化测试可确保在修复这些要求的成本较低时及早发现任何影响满足这些要求的问题。这些对系统非功能性行为的测试使开发人员能够根据经验证据进行重构和重新架构:“最近对搜索的更改导致应用程序的性能下降——我们需要修改解决方案以确保我们满足我们的能力要求。”

These tests do not just test the functional aspects of the system. Capacity, security, and other nonfunctional requirements are established early on, and automated test suites are written to enforce them. These automated tests ensure that any problems that compromise the fulfillment of these requirements are caught early when the cost of fixing them is low. These tests of the nonfunctional behaviors of the system enable developers to refactor and rearchitect on the basis of empirical evidence: “The recent changes to the search have caused the performance of the application to degrade—we need to modify the solution to ensure that we meet our capacity requirements.”

在早期采用适当纪律的项目中,这个理想世界是完全可以实现的。如果您需要在一个已经运行了一段时间的项目上实现它们,事情就会稍微困难一些。达到高水平的自动化测试覆盖率需要时间和周密的计划,以确保在团队学习如何实施自动化测试的同时开发可以继续。遗留代码库肯定会受益于其中许多技术,尽管它们可能需要很长时间才能达到从一开始就使用自动化测试构建的系统的质量水平。我们将在本章后面讨论将这些技术应用于遗留系统的方法。

This ideal world is fully achievable in projects that adopt the appropriate discipline early on. If you need to implement them on a project that has already been running for some time, things are a little more difficult. Getting to a high level of automated test coverage will take time and careful planning to ensure that development can continue while teams learn how to implement automated testing. Legacy codebases will certainly benefit from many of these techniques, although it may take a long time until they reach the level of quality of a system built from the start with automated tests. We discuss ways to apply these techniques to legacy systems later on in this chapter.

测试策略的设计主要是识别项目风险并确定其优先级并决定采取哪些措施来减轻这些风险的过程。一个好的测试策略有很多积极的影响。测试建立了对软件正常工作的信心,这意味着错误更少、支持成本更低并提高了声誉。测试还对鼓励良好开发实践的开发过程提供约束。一个全面的自动化测试套件甚至提供最完整和最新形式的应用程序文档,以可执行规范的形式,不仅说明系统应该如何工作,而且还说明它实际如何工作。

The design of a testing strategy is primarily a process of identifying and prioritizing project risks and deciding what actions to take to mitigate them. A good testing strategy has many positive effects. Testing establishes confidence that the software is working as it should, which means fewer bugs, reduced support costs, and improved reputation. Testing also provides a constraint on the development process which encourages good development practices. A comprehensive automated test suite even provides the most complete and up-to-date form of application documentation, in the form of an executable specification not just of how the system should work, but also of how it actually does work.

最后,值得注意的是,我们在这里只能触及测试的表面。我们的目的是涵盖自动化测试的基础知识,为本书的其余部分提供足够的上下文来理解,并使您能够为您的项目实施合适的部署管道。特别是,我们不会深入测试实施的技术细节,也不会详细讨论探索性测试等主题。有关测试的更多详细信息,我们建议您查看本书的姊妹篇之一:Lisa Crispin 和 Janet Gregory 的敏捷测试(Addison-Wesley,2009 年)。

Finally, it’s worth noting that we can only scratch the surface of testing here. Our intention is to cover the fundamentals of automated testing, providing enough context for the rest of the book to make sense, and to enable you to implement a suitable deployment pipeline for your project. In particular, we don’t dive into the technical details of test implementation, nor do we cover topics such as exploratory testing in detail. For more detail on testing, we suggest you look at one of the companion volumes to this book: Lisa Crispin and Janet Gregory’s Agile Testing (Addison-Wesley, 2009).

测试类型

Types of Tests

图 4.1 测试象限图,由 Brian Marick 绘制,基于当时“悬而未决”的想法

Figure 4.1 Testing quadrant diagram, due to Brian Marick, based on ideas that were “in the air” at the time

图片

存在多种测试。Brian Marick 提出了图 4.1,该图被广泛用于模拟您应该进行的各种类型的测试,以确保交付高质量的应用程序。

Many kinds of testing exist. Brian Marick came up with Figure 4.1, which is widely used to model the various types of tests that you should have in place to ensure the delivery of a high-quality application.

在这张图中,他根据测试是面向业务还是面向技术,以及它们是支持开发过程还是用于批评项目对测试进行了分类。

In this diagram, he categorized tests according to whether they are business-facing or technology-facing, and whether they support the development process or are used to critique the project.

支持开发过程的面向业务的测试

Business-Facing Tests That Support the Development Process

该象限中的测试通常称为功能测试或验收测试。验收测试确保满足故事的验收标准。在故事开始开发之前,应该编写验收测试,最好是自动化测试。验收测试与验收标准一样,可以测试正在构建的系统的各种属性,包括功能、容量、可用性、安全性、可修改性、可用性等。涉及系统功能的验收测试被称为功能验收测试——非功能验收测试属于该图的第四象限。有关功能测试和非功能测试之间有些模糊且经常被误解的区别的更多信息,请查看下面我们对批评该项目的面向技术的测试的报道。

The tests in this quadrant are more commonly known as functional or acceptance tests. Acceptance testing ensures that the acceptance criteria for a story are met. Acceptance tests should be written, and ideally automated, before development starts on a story. Acceptance tests, like acceptance criteria, can test all kinds of attributes of the system being built, including functionality, capacity, usability, security, modifiability, availability, and so on. Acceptance tests that concern the functionality of the system are known as functional acceptance tests—nonfunctional acceptance tests fall into the fourth quadrant of the diagram. For more on the somewhat blurry and often misunderstood distinction between functional and nonfunctional tests, take a look at our coverage of technology-facing tests that critique the project, below.

验收测试在敏捷环境中至关重要,因为它们回答了“我怎么知道什么时候完成了?”这样的问题。对于开发人员和“我得到了我想要的吗?” 对于用户。当验收测试通过时,他们正在测试的任何需求或故事都可以说是完整的。因此,在理想情况下,客户或用户会编写验收测试,因为他们为每个需求定义了成功标准。现代自动化功能测试工具,如 Cucumber、JBehave、Concordion 和 Twist,旨在通过将测试脚本与实现分离来实现这一理想,同时提供一种机制使它们保持同步变得简单。通过这种方式,用户可以编写测试脚本,而开发人员和测试人员可以共同编写实现它们的代码。

Acceptance tests are critical in an agile environment because they answer the questions, “How do I know when I am done?” for developers and “Did I get what I wanted?” for users. When the acceptance tests pass, whatever requirements or stories they are testing can be said to be complete. Thus, in an ideal world, customers or users would write acceptance tests, since they define the success criteria for each requirement. Modern automated functional testing tools, such as Cucumber, JBehave, Concordion, and Twist, aim to realize this ideal by separating the test scripts from the implementation, while providing a mechanism that makes it simple to keep them synchronized. In this way, it is possible for users to write the test scripts, while developers and testers work together on the code that implements them.

通常,对于每个故事或需求,根据用户将要执行的操作,应用程序都有一个规范的路径。这被称为幸福之路这通常使用以下形式表达“给定[测试开始时系统状态的一些重要特征],当[用户执行某些操作]时,然后[系统的一些重要特征系统的新状态]将会产生。” 这有时被称为测试的“当时给定”模型。

In general, for each story or requirement there is a single canonical path through the application in terms of the actions that the user will perform. This is known as the happy path. This is often expressed using the form “Given [a few important characteristics of the state of the system when testing begins], when [the user performs some set of actions], then [a few important characteristics of the new state of the system] will result.” This is sometimes referred to as the “given-when-then” model for tests.

但是,除了最简单的系统之外,任何用例都将允许初始状态、要执行的操作和应用程序的最终状态发生变化。有时,这些变体构成不同的用例,然后称为替代路径在其他情况下,它们应该会导致错误情况,从而导致所谓的sad paths显然,可以使用这些变量的不同值执行许多可能的测试。等价划分分析和边界值分析会将这些可能性减少到一个较小的案例集,这些案例将完全测试相关需求。然而,即便如此,您仍需要使用您的直觉来选择最相关的案例。

However, any use case will, in all but the simplest of systems, allow for variations in the initial state, the actions to be performed, and the final state of the application. Sometimes, these variations constitute distinct use cases, which are then known as alternate paths. In other cases, they should cause error conditions, resulting in what is called sad paths. There are clearly many possible tests that can be performed with different values for these variables. Equivalence partitioning analysis and boundary value analysis will reduce these possibilities to a smaller set of cases that will completely test the requirement in question. However, even then you’ll need to use your intuition to pick the most relevant cases.

当您的系统处于类似于生产的模式时,应该运行验收测试。手动验收测试通常通过将应用程序置于用户验收测试 (UAT) 环境中来完成,该环境在配置和应用程序状态方面都与生产环境尽可能相似——尽管它可能使用任何外部服务的模拟版本. 测试人员使用应用程序的标准用户界面来执行测试。自动化验收测试应该类似地在类似生产的环境中运行,测试工具与应用程序交互的方式与用户交互的方式相同。

Acceptance tests should be run when your system is in a production-like mode. Manual acceptance testing is typically done by putting an application in a user acceptance testing (UAT) environment which is as similar as possible to production both in configuration and in terms of the state of the application—although it might use mock versions of any external services. The tester uses the application’s standard user interface in order to perform testing. Automated acceptance tests should similarly be run in a production-like environment, with the test harness interacting with the application the same way that a user would.

自动化验收测试

Automating Acceptance Tests

自动化验收测试具有许多有价值的特性:

Automated acceptance tests have a number of valuable properties:

• 它们使反馈循环更快——开发人员可以运行自动化测试以确定他们是否已完成特定需求,而无需去找测试人员。

• They make the feedback loop faster—developers can run automated tests to find out if they have completed a particular requirement without having to go to testers.

• 它们减少了测试人员的工作量。

• They reduce the workload on testers.

• 它们使测试人员能够专注于探索性测试和更高价值的活动,而不是枯燥的重复性任务。

• They free testers to concentrate on exploratory testing and higher-value activities instead of boring repetitive tasks.

• 您的验收测试代表了一个强大的回归测试套件。这在编写大型应用程序或在使用框架或许多模块的大型团队中工作并且对应用程序的一部分进行更改可能会影响其他功能时尤为重要。

• Your acceptance tests represent a powerful regression test suite. This is particularly important when writing large applications or working in large teams where frameworks or many modules are being used and changes to one part of the application are likely to affect other features.

• 通过使用人类可读的测试和测试套件名称,正如行为驱动开发所提倡的那样,可以从您的测试中自动生成需求文档。实际上,像 Cucumber 和 Twist 这样的工具旨在让分析师将需求编写为可执行的测试脚本。这种方法的好处是您的需求文档永远不会过时——它可以在每次构建时自动生成。

• By using human-readable test and test suite names, as advocated by behavior-driven development, it is possible to autogenerate requirements documentation from your tests. Indeed, tools like Cucumber and Twist are designed to allow analysts to write requirements as executable test scripts. The benefit of this approach is that your requirements documentation is never out-of-date—it can be generated automatically with every build.

回归测试的问题尤为重要。象限图中没有提到回归测试,因为它们是一个横切类别。回归测试代表自动化测试的整个语料库。它们用于确保在您进行更改时不会破坏现有功能。它们还可以通过验证在重构完成时您没有更改任何行为来轻松重构代码。在编写自动化验收测试时,您应该记住它们将构成您的回归测试套件的一部分。

The question of regression testing is particularly important. Regression tests aren’t mentioned on the quadrant diagram because they are a crosscutting category. Regression tests represent the entire corpus of your automated tests. They serve to ensure that when you make a change you don’t break existing functionality. They also make it possible to easily refactor code by verifying that you haven’t changed any behavior when refactoring is done. When writing automated acceptance tests, you should keep in mind that they will form part of your regression test suite.

然而,自动验收测试的维护成本可能很高。如果做得不好,它们会给您的交付团队带来巨大的成本。出于这个原因,有些人建议不要创建大型复杂的自动化测试套件。1但是,通过遵循良好实践并使用适当的工具,可以显着降低创建和维护自动化验收测试的成本,使收益明显超过成本。我们将在第 8 章“自动验收测试”中更详细地讨论这些技术

However, automated acceptance tests can be costly to maintain. Done badly, they can inflict a significant cost on your delivery team. For this reason, some people recommend against creating large complex suites of automated tests.1 However, by following good practices and using appropriate tools, it is possible to dramatically reduce the cost of creating and maintaining automated acceptance tests to the point where the benefits clearly exceed the costs. We discuss these techniques in more detail in Chapter 8, “Automated Acceptance Testing.”

重要的是要记住,并非所有事情都需要自动化。人们真正擅长测试的系统有很多方面。可用性、外观和感觉的一致性等是自动化测试中难以验证的事情。探索性测试也不可能自动进行——当然,测试人员将自动化用作探索性测试的一部分,例如设置场景和创建测试数据。在许多情况下,手动测试就足够了,或者确实优于自动测试。通常,我们倾向于将我们的自动化验收测试限制为完全覆盖快乐路径行为,而仅覆盖最重要的其他部分。这是一种安全有效的策略,假设您已经拥有一套全面的其他类型的自动回归测试。我们通常将全面性归类为大于 80% 的代码覆盖率,尽管测试的质量非常重要,而仅覆盖率是一个很差的指标。这种情况下的自动化测试覆盖率包括单元测试、组件测试和验收测试,每个测试都应覆盖应用程序的 80%(我们不赞成这样的天真想法,即您可以通过 60% 的单元测试覆盖率和 20% 的单元测试覆盖率获得 80% 的覆盖率) % 验收测试覆盖率)。

It’s important to remember that not everything needs to be automated. There are many aspects of a system that people are genuinely better at testing. Usability, consistency of look and feel, and so on are difficult things to verify in automated tests. Exploratory testing is also impossible to do automatically—although, of course, testers use automation as part of exploratory testing for things like setting up scenarios and creating test data. In many cases, manual testing can suffice, or indeed can be superior to automated tests. In general, we tend to limit our automated acceptance testing to complete coverage of happy path behaviors and only limited coverage of the most important other parts. This is a safe and efficient strategy, assuming that you already have a comprehensive set of automated regression tests of other kinds. We generally class comprehensive as greater than 80% code coverage, though the quality of the tests is very important and coverage alone is a poor metric. Automated test coverage in this context includes unit, component, and acceptance tests, each of which should cover 80% of the application (we don’t subscribe to the naive idea that you can gain 80% coverage with 60% unit test coverage and 20% acceptance test coverage).

作为自动验收测试覆盖率的良好试金石,请考虑以下场景。假设您更换了我们系统的某些部分——例如持久层——并用不同的实现替换它。您完成替换,运行自动验收测试,它们通过了。您对您的系统真正运行有多大信心?一个好的自动化测试套件应该让您有信心执行重构,甚至重新设计您的应用程序,知道如果测试通过,您的应用程序的行为确实没有受到影响。

As a good litmus test of your automated acceptance test coverage, consider the following scenario. Suppose you swap out some part of our system—such as the persistence layer—and replace it with a different implementation. You complete the replacement, run your automated acceptance tests, and they pass. How confident do you feel that your system is really working? A good automated test suite should give you the confidence necessary to perform refactorings and even rearchitecting of your application knowing that if the tests pass, your application’s behavior really hasn’t been affected.

与软件开发的每个其他方面一样,每个项目都是不同的,您需要监控在重复手动操作上花费了多少时间测试,以便您可以决定何时将它们自动化。一个好的经验法则是,一旦您重复了几次相同的测试,并且您确信您最终不会花费大量时间来维护测试时,就可以实现自动化。有关何时自动化的更多信息,请阅读 Brian Marick 的论文“何时应将测试自动化?” [90NC1y]。

As with every other aspect of software development, each project is different, and you need to monitor how much time is being spent on repeating manual tests so you can decide when to automate them. A good rule of thumb is to automate once you have repeated the same test a couple of times, and when you are confident that you won’t end up spending a lot of time maintaining the test. For more on when to automate, read Brian Marick’s paper “When Should a Test Be Automated?” [90NC1y].

要编写的最重要的自动化测试是主要的快乐路径测试。每个故事或需求都应该至少有一个自动化的快乐路径验收测试。这些测试应该由开发人员单独使用作为冒烟测试,以提供关于他们是否破坏了他们正在开发的某些功能的快速反馈。它们应该是自动化的首要目标。

The most important automated test to write is the main happy path test. Every story or requirement should have at least one automated happy path acceptance test. These tests should be used individually by developers as smoke tests to provide rapid feedback on whether they have broken some bit of functionality they are working on. They should be the first target for automation.

当您有时间编写和自动化进一步的测试时,很难在交替的快乐路径和悲伤路径之间做出选择。如果您的应用程序相当稳定,那么替代路径应该是您的优先事项,因为它们代表所有用户定义的场景。如果您的应用程序存在错误并且经常崩溃,战略性地应用悲伤路径测试可以帮助您识别问题区域并修复它们,而自动化可以确保应用程序保持稳定。

When you have time to write and automate further tests, it’s hard to choose between alternate happy paths and sad paths. If your application is reasonably stable, then alternate paths should be your priority since they represent all the user-defined scenarios. If your application is buggy and crashes often, strategic application of sad path testing can help you identify problem areas and fix them, and automation can ensure that the application remains stable.

支持开发过程的面向技术的测试

Technology-Facing Tests That Support the Development Process

这些自动化测试由开发人员专门编写和维护。此类测试分为三种:单元测试、组件测试和部署测试。单元测试单独测试一段特定的代码。出于这个原因,他们通常依赖于使用测试替身来模拟系统的其他部分(参见第 91 页的“测试替身”部分). 单元测试不应该涉及调用数据库、使用文件系统、与外部系统对话,或者通常情况下,系统组件之间的交互。这使它们运行得非常快,因此您可以及早获得有关更改是否破坏了任何现有功能的反馈。这些测试还应涵盖系统中的几乎所有代码路径(至少 80%)。因此,它们构成了回归测试套件的关键部分。

These automated tests are written and maintained exclusively by developers. There are three kinds of tests that fall into this category: unit tests, component tests, and deployment tests. Unit tests test a particular piece of the code in isolation. For this reason, they often rely on simulating other parts of the system using test doubles (see the “Test Doubles” section on page 91). Unit tests should not involve calling the database, using the filesystem, talking to external systems, or, in general, interaction between components of a system. This enables them to run very fast so you can get early feedback on whether changes have broken any existing functionality. These tests should also cover virtually every code-path in the system (a bare minimum of 80%). Thus they form a key part of your regression test suite.

然而,这种速度是以遗漏由于应用程序的各个部分之间的交互而发生的那些错误为代价的。例如,对象(在 OO 编程中)或应用程序数据位具有非常不同的生命周期是很常见的。只有通过测试更大的应用程序块,您才会发现由于数据或对象的生命周期未得到正确管理而导致的错误。

However, this speed comes at the cost of missing those bugs that occur as a result of interaction between the various pieces of your application. For example, it is very common for objects (in OO programming) or bits of application data to have very different lifecycles. It is only by testing larger chunks of your application that you will find bugs occurring due to the lifecycles of your data or objects not being managed correctly.

组件测试测试更大的功能集群,以便它们可以捕获此类问题。它们通常较慢,因为它们可能需要更多复杂的设置并执行更多 I/O,与数据库、文件系统或其他系统对话。有时,组件测试被称为“集成测试”——但术语“集成测试”已经过载,因此我们不会在本书的这种情况下使用它。

Component tests test larger clusters of functionality, so that they can catch problems like these. They are typically slower, since they can require more involved setup and perform more I/O, talking to databases, the filesystem, or other systems. Sometimes, component tests are known as “integration tests”—but the term “integration tests” is overloaded, so we won’t use it in this context in the book.

每当您部署应用程序时都会执行部署测试。他们检查部署是否有效——换句话说,您的应用程序是否已正确安装、正确配置、能够联系它需要的任何服务,并且它正在响应。

Deployment tests are performed whenever you deploy your application. They check that the deployment worked—in other words, that your application is correctly installed, correctly configured, able to contact any services it requires, and that it is responding.

批评项目的面向业务的测试

Business-Facing Tests That Critique the Project

这些手动测试验证应用程序实际上会向用户提供他们期望的价值。这不仅仅是验证应用程序是否符合其规范的问题;它还与检查规格是否正确有关。我们从未参与或听说过提前完美指定应用程序的项目。不可避免地,当用户在现实生活中尝试应用程序时,他们会发现还有改进的余地。他们破坏事物是因为他们设法执行了以前没有人尝试过的一系列操作。他们抱怨该应用程序可以更好地帮助他们完成他们最常执行的任务。也许他们受到应用程序的启发,并确定了可以为他们带来更多价值的新功能。

These manual tests verify that the application will in fact deliver to the users the value they are expecting. This is not just a matter of verifying that the application meets its specifications; it is also about checking that the specifications are correct. We have never worked on, or heard of, a project where the application was specified perfectly in advance. Inevitably, when users try an application in real life, they discover that there is room for improvement. They break things because they manage to perform sets of operations that nobody had tried before. They complain that the application could be better at helping them with the tasks that they perform most often. Perhaps they are inspired by the application and identify new features that will give them even more value. Software development is a naturally iterative process that thrives on the establishment of effective feedback loops, and we deceive ourselves if we perceive it any other way.

一种特别重要的面向业务的项目评论测试形式是展示。敏捷团队在每次迭代结束时向用户展示,以展示他们交付的新功能。在开发过程中还应尽可能多地向客户演示功能,以确保尽早发现任何误解或规范问题。成功的展示既可以是福也可以是祸——用户喜欢接触新东西并尝试使用它。但他们总是有很多改进建议。此时,客户和项目团队必须决定他们想要对项目计划进行多大程度的更改以纳入这些建议。不管结果如何,尽早获得反馈比在项目结束时进行更改为时已晚要好得多。展示是任何项目的核心:这是你第一次可以说一件作品真正完成后让人们满意,毕竟他们是付账的人。

A particularly important form of business-facing, project-critique tests are showcases. Agile teams perform showcases to users at the end of every iteration to demonstrate the new functionality that they have delivered. Functionality should also be demonstrated to customers as often as possible during development, so as to ensure that any misunderstandings or specification problems are caught as early as possible. Showcases that go well can be both a blessing and a curse—users love getting their hands on new stuff and playing around with it. But they invariably have plenty of suggestions for improvement. At this point, the customer and the project team have to decide how much they want to change the project’s plan to incorporate these suggestions. Whatever the outcome, it’s much better to get feedback early rather than at the end of the project when it’s too late to make changes. Showcasing is the heartbeat of any project: It is the first time that you can say that a piece of work is really done to the satisfaction of the people who are, after all, paying the bills.

James Bach 将探索性测试描述为手动测试的一种形式,其中“测试人员在执行这些测试时主动控制测试的设计,并使用在测试时获得的信息来设计新的和更好的测试。”2探索性测试是一种创造性的学习过程,它不仅会发现错误,还会导致创建新的自动化测试集,并可能为应用程序提供新的需求。

Exploratory testing is described by James Bach as a form of manual testing in which “the tester actively controls the design of the tests as those tests are performed and uses information gained while testing to design new and better tests.”2 Exploratory testing is a creative learning process that will not only discover bugs, but also lead to the creation of new sets of automated tests, and potentially feed into new requirements for the application.

进行可用性测试是为了发现用户使用您的软件实现目标的难易程度。在开发过程中很容易过于接近问题,即使对于致力于指定应用程序的非技术人员也是如此。因此,可用性测试是您的应用程序实际将向用户交付价值的最终测试。可用性测试有几种不同的方法,从上下文查询到让用户坐在您的应用程序前并拍摄他们执行常见任务的过程。可用性测试人员收集指标,记录用户完成他们的任务需要多长时间,注意人们是否按错了按钮,记录他们找到正确的文本字段需要多长时间,并让他们在最后记录他们的满意度.

Usability testing is done to discover how easy it is for users to accomplish their goals with your software. It is easy to get too close to the problem during development, even for nontechnical people working on specifying the application. Usability testing is therefore the ultimate test that your application is actually going to deliver value to users. There are several different approaches to usability testing, from contextual enquiry to sitting users down in front of your application and filming them performing common tasks. Usability testers gather metrics, noting how long it takes users to finish their tasks, watching out for people pressing the wrong buttons, noting how long it takes them to find the right text field, and getting them to record their level of satisfaction at the end.

最后,您可以使用 Beta 测试程序将您的应用程序提供给真实用户。事实上,许多网站似乎永远处于测试状态。一些更具前瞻性的网站(例如 Netflix)不断向选定的用户发布新功能,而他们甚至没有注意到。许多组织使用金丝雀发布(请参阅第 263 页的“金丝雀发布”部分),其中应用程序的几个细微不同的版本同时投入生产,并比较它们的有效性。这些组织收集有关新功能如何使用的统计数据,如果它不能提供足够的价值就将其淘汰。这为采用非常有效的功能提供了一种进化方法。

Finally, you can give your application to real users using beta testing programs. Indeed, many websites seem to be perpetually in a beta state. Some of the more forward-thinking sites (NetFlix, for example) continually release new features to selected users without them even noticing. Many organizations use canary releasing (see the “Canary Releasing” section on page 263) where several subtly different versions of the application are in production simultaneously and their effectiveness is compared. These organizations gather statistics on how the new functionality gets used, and retire it if it doesn’t deliver sufficient value. This provides an evolutionary approach to the adoption of features which is very effective.

批评项目的面向技术的测试

Technology-Facing Tests That Critique the Project

验收测试分为两类:功能测试和非功能测试。非功能性测试是指系统除功能之外的所有质量,例如容量、可用性、安全性等。正如我们上面提到的,功能测试和非功能测试之间的区别在某些方面是虚假的,就像这些测试不面向业务的想法一样。这似乎是显而易见的,但许多项目并没有像对待其他需求那样对待非功能性需求,或者(更糟的是)根本懒得去验证它们。尽管用户很少花大量时间预先指定容量和安全特性,但如果他们的信用卡详细信息被盗或网站因容量问题而不断关闭,他们肯定会非常沮丧。为此原因,许多人认为“非功能性需求”是一个糟糕的名称,并提出了替代方案,例如跨功能需求或系统特征。尽管我们赞成这种立场,但我们在整本书中都将它们称为非功能性特征,因此每个人都知道我们在说什么。无论您如何称呼它们,非功能性验收标准都应该以与功能性验收标准完全相同的方式指定为应用程序要求的一部分。

Acceptance testing comes in two categories: functional tests and nonfunctional tests. By nonfunctional tests, we mean all the qualities of a system other than its functionality, such as capacity, availability, security, and so forth. As we mention above, the distinction between functional and nonfunctional testing is in some ways bogus, as is the idea that these tests are not business-facing. This may seem obvious, but many projects do not treat nonfunctional requirements the same way as other requirements or (worse) do not bother to validate them at all. Although users rarely spend a lot of time specifying capacity and security characteristics up front, they will certainly be very upset if their credit card details are stolen or if a website is constantly down due to capacity problems. For this reason, it has been argued by many people that “nonfunctional requirements” is a bad name, with alternatives suggested such as cross-functional requirements or system characteristics. Although we are sympathetic to this position, we have referred to them throughout this book as nonfunctional characteristics so everybody knows what we’re talking about. Whatever you call them, nonfunctional acceptance criteria should be specified as part of your application’s requirements in exactly the same way as functional acceptance criteria.

用于检查是否满足这些验收标准的测试以及用于运行测试的工具往往与用于验证是否符合功能验收标准的工具大不相同。这些测试通常需要大量资源,例如运行的特殊环境以及设置和实施的专业知识,而且通常需要很长时间才能运行(无论它们是否自动化)。因此,它们的实施往往会被推迟。即使它们是完全自动化的,它们的运行频率也往往低于功能验收测试,并且在部署管道的下游更靠后。

The tests used to check whether these acceptance criteria have been met, and the tools used to run the tests tend to be quite different from those used to verify conformance to functional acceptance criteria. These tests often require considerable resources such as special environments to run on and specialized knowledge to set up and implement, and they often take a long time to run (whether or not they are automated). Therefore, their implementation tends to be deferred. Even when they are fully automated, they tend to be run less frequently and further down the deployment pipeline than the functional acceptance tests.

然而,事情正在发生变化。用于执行这些测试的工具正在成熟,用于开发它们的技术也变得更加主流。在发布前多次因性能不佳而陷入困境,我们建议您在任何项目开始时至少设置一些基本的非功能性测试,无论多么简单或无关紧要。对于更复杂或关键任务的项目,您应该考虑从项目一开始就分配项目时间来研究和实施非功能性测试。

However, things are changing. The tools used to perform these tests are maturing, and the techniques used to develop them are becoming more mainstream. Having been caught short many times by bad performance just before release, we recommend that you set up at least some basic nonfunctional tests towards the start of any project, no matter how simple or inconsequential. For more complex or mission-critical projects, you should consider allocating project time to researching and implementing nonfunctional testing from the start of your project.

测试双打

Test Doubles

自动化测试的一个关键部分涉及在运行时用模拟版本替换系统的一部分。通过这种方式,被测应用程序的一部分与应用程序其余部分的交互可以被严格约束,从而可以更容易地确定其行为。这种模拟通常被称为模拟、存根、虚拟等等。我们将遵循 Gerard Meszaros 在他的书xUnit Test Patterns中使用的术语,总结如下马丁·福勒 [aobjRH]。Meszaros 创造了通用术语“测试替身”,并进一步区分了各种类型的测试替身,如下所示:

A key part of automated testing involves replacing part of a system at run time with a simulated version. In this way, the interactions of the part of the application under test with the rest of the application can be tightly constrained, so that its behavior can be determined more easily. Such simulations are often known as mocks, stubs, dummies, and so forth. We’ll be following the terminology that Gerard Meszaros uses in his book xUnit Test Patterns, as summarized by Martin Fowler [aobjRH]. Meszaros coined the generic term “test doubles” and distinguishes further between the various types of test doubles as follows:

• 虚拟对象被传递但从未真正使用过。通常它们只是用来填充参数列表。

• Dummy objects are passed around but never actually used. Usually they are just used to fill parameter lists.

• 假对象实际上有工作实现,但通常采取一些使它们不适合生产的捷径。内存数据库就是一个很好的例子。

• Fake objects actually have working implementations, but usually take some shortcut that makes them not suitable for production. A good example of this is the in-memory database.

• 存根为测试期间发出的呼叫提供预制答案,通常根本不响应测试程序之外的任何内容。

• Stubs provide canned answers to the calls made during the test, usually not responding at all to anything outside what’s programmed in for the test.

• 间谍是一些存根,它们还根据调用方式记录一些信息。其中一种形式可能是电子邮件服务,它记录发送了多少条消息。

• Spies are stubs that also record some information based on how they were called. One form of this might be an email service that records how many messages it was sent.

• 模拟程序预先设定了预期,形成了它们预期接收的呼叫的规范。如果他们收到他们不期望的电话,他们可以抛出异常,并在验证期间进行检查以确保他们收到了他们期望的所有电话。

• Mocks are preprogrammed with expectations that form a specification of the calls they are expected to receive. They can throw an exception if they receive a call they don’t expect and are checked during verification to ensure they got all the calls they were expecting.

模拟是一种特别被滥用的测试替身形式。很容易通过编写既无意义又脆弱的测试来滥用模拟,使用它们只是为了断言某些代码工作的具体细节,而不是它与协作者的交互。这种用法很脆弱,因为如果实现发生变化,测试就会中断。检查模拟和存根之间的区别超出了本书的范围,但您将在第 8 章“自动验收测试”中找到更多详细信息。阐述如何正确使用模拟的最全面的论文可能是“Mock Roles, Not Objects”[duZRWb]。Martin Fowler 在他的文章“Mocks Aren't Stubs”[dmXRSC] 中也给出了一些建议。

Mocks are an especially abused form of test doubles. It’s very easy to misuse mocks by writing tests that are both pointless and fragile, using them simply to assert the specific details of the workings of some code rather than its interactions with collaborators. Such usage is fragile because if the implementation changes, the test breaks. Examining the distinction between mocks and stubs goes beyond the scope of this book, but you’ll find more detail in Chapter 8, “Automated Acceptance Testing.” Probably the most comprehensive paper laying out how to use mocks correctly is “Mock Roles, Not Objects” [duZRWb]. Martin Fowler also gives some pointers in his article “Mocks Aren’t Stubs” [dmXRSC].

现实生活中的情况和策略

Real-Life Situations and Strategies

以下是决定自动化测试的团队面临的一些典型场景。

Here are some typical scenarios faced by teams who have decided to automate their tests.

新项目

New Projects

新项目代表了实现我们在本书中描述的理想的机会。在这个阶段,变更的成本很低,通过建立一些相对简单的基本规则并创建一些相对简单的测试基础架构,您可以为持续集成过程提供一个良好的开端。在这种情况下,重要的是从一开始就开始编写自动化验收测试。为此,您需要:

New projects represent a chance to achieve the ideals that we describe in this book. At this stage, the cost of change is low and, by establishing some relatively simple ground rules and creating some relatively simple test infrastructure, you can give a great start to your process of continuous integration. In this situation, the important thing is to start writing automated acceptance tests from the very beginning. In order to do this, you’ll need:

• 选择技术平台和测试工具。

• To choose a technology platform and testing tools.

• 设置简单的自动化构建。

• To set up a simple, automated build.

• 制定遵循INVEST 原则[ddVMFH] 的故事(它们应该是独立的、可协商的、有价值的、可估计的、小的和可测试的),以及验收标准。

• To work out stories that follow the INVEST principles [ddVMFH] (they should be Independent, Negotiable, Valuable, Estimable, Small, and Testable), with acceptance criteria.

然后您可以实施严格的流程:

You can then implement a strict process:

• 客户、分析师和测试人员定义验收标准。

• Customers, analysts, and testers define acceptance criteria.

• 测试人员与开发人员合作,根据验收标准自动进行验收测试。

• Testers work with developers to automate acceptance tests based on the acceptance criteria.

• 开发人员对行为进行编码以满足验收标准。

• Developers code behavior to fulfill the acceptance criteria.

• 如果任何自动化测试失败(无论是单元测试、组件测试还是验收测试),开发人员都会优先修复它们。

• If any automated tests fail (whether unit, component, or acceptance tests), developers make it a priority to fix them.

在项目开始时采用此过程比在几次迭代后决定需要验收测试要简单得多。在这些后期阶段,您不仅必须尝试并想出实施验收测试的方法,因为对它们的支持在您的框架中还不存在——您还必须说服持怀疑态度的开发人员需要孜孜不倦地遵循这个过程。如果您从项目的开始就开始,那么让团队沉迷于自动化测试更容易实现。

It is much simpler to adopt this process at the start of a project than decide a few iterations later that you need acceptance tests. At these later stages, not only will you have to try and to come up with ways to implement the acceptance tests, since support for them won’t already exist in your framework—you’ll also have to convince skeptical developers of the need to follow the process assiduously. Getting a team addicted to automated testing is simpler to achieve if you start at the beginning of a project.

然而,团队中的每个人,包括客户和项目经理,都被这些好处所接受也很重要。我们已经看到项目被取消,因为客户认为在自动化验收测试上花费了太多时间。如果客户真的宁愿牺牲他们的自动化验收测试套件的质量以将其快速推向市场,他们也有权做出该决定——但后果应该非常清楚。

However, it is also essential that everybody on the team, including customers and project managers, are bought in to these benefits. We have seen projects cancelled because the customer felt that too much time was spent working on automated acceptance tests. If the customer really would rather sacrifice the quality of their automated acceptance test suite in order to get it to market quickly, they are entitled to make that decision—but the consequences should be made quite clear.

最后,重要的是要确保仔细编写您的验收标准,以便它们从用户的角度表达故事所传达的商业价值。盲目地自动化编写糟糕的验收标准是无法维护验收测试套件的主要原因之一。对于您编写的每个验收标准,应该可以编写一个自动验收测试来证明所描述的价值已交付给用户。这意味着测试人员应该从一开始就参与编写需求,确保在整个系统演化过程中支持连贯、可维护的自动化验收测试套件。

Finally, it is important to make sure that your acceptance criteria are carefully written so that they express the business value that the story delivers from the point of view of the user. Blindly automating badly written acceptance criteria is one of the major causes of unmaintainable acceptance test suites. For each acceptance criterion you write, it should be possible to write an automated acceptance test proving that the value described is delivered to the user. This means that testers should be involved in writing requirements from the start, ensuring that a coherent, maintainable automated acceptance test suite is supported throughout the evolution of the system.

在这个过程之后,我们描述了开发人员编写代码的方式的变化。比较从一开始就使用自动验收测试开发的代码库与那些使用验收测试的代码库事后想想,在前一种情况下,我们几乎总能看到更好的封装、更清晰的意图、更清晰的关注点分离和更多的代码重用。这确实是一个良性循环:在正确的时间进行测试会产生更好的代码。

Following the process we describe changes the way developers write code. Comparing codebases that have been developed using automated acceptance tests from the beginning with those where acceptance testing has been an afterthought, we almost always see better encapsulation, clearer intent, cleaner separation of concerns, and more reuse of code in the former case. This really is a virtuous circle: Testing at the right time leads to better code.

中期项目

Midproject

虽然从头开始一个项目总是令人愉快的,但现实是我们经常发现自己在一个资源匮乏的大型团队中工作,开发一个快速变化的代码库,承受着交付的压力。

Although it’s always pleasant to be starting a project from scratch, the reality is that we often find ourselves working on a large, resource-starved team developing a rapidly changing codebase, under pressure to deliver.

引入自动化测试的最佳方式是从应用程序最常见、最重要和最有价值的用例开始。这将需要与您的客户进行对话,以清楚地确定真正的业务价值所在,然后通过测试保护此功能免受回归。基于这些对话,您应该自动化涵盖这些高价值场景的快乐路径测试。

The best way to introduce automated testing is to begin with the most common, important, and high-value use cases of the application. This will require conversations with your customer to clearly identify where the real business value lies, and then defending this functionality against regressions with tests. Based on these conversations you should automate happy path tests that cover these high-value scenarios.

此外,最大限度地增加这些测试涵盖的操作数量也很有用。让它们涵盖比您通常使用故事级验收测试解决的场景更广泛的场景。填写尽可能多的字段并按下尽可能多的按钮以满足测试的需要。这种方法为这些核心行为测试中正在测试的功能提供了一些广泛的覆盖,即使这些测试不会突出系统细节中的故障或更改。例如,您会知道系统的基本行为正常,但可能会忽略某些验证无效的事实。这样做的好处是使手动测试更有效一些,因为您不必测试每个字段。

In addition, it is useful to maximize the number of actions that these tests cover. Make them cover slightly broader scenarios than you would normally address with story-level acceptance tests. Fill in as many fields as possible and press as many buttons as possible to satisfy the needs of the test. This approach gives some broad cover for the functionality being tested in these core behavioral tests, even though the tests won’t highlight failures or changes in the details of the system. For example, you will know that the basic behavior of you system is working, but may miss the fact that some validations are not. This has the bonus of making manual testing a little more efficient, since you won’t have to test every single field. You’ll be sure that builds that have passed automated tests will function correctly and deliver business value even if some aspects of their behavior aren’t as you would wish.

该策略意味着,由于您只是在自动化快乐路径,因此您将不得不执行相应大量的手动测试,以确保您的系统完全正常工作。您应该会发现手动测试变化很快,因为它们将测试新功能或新更改的功能。当您发现您多次手动测试同一功能时,请检查并查看该功能是否可能会发生变化。如果没有,请自动化测试。相反,如果您发现自己花费大量时间修复特定测试,则可以假设被测功能正在发生变化。如果是这种情况,请再次与客户和开发团队核实。如果是这样,通常可以告诉您的自动化测试框架忽略测试,记住在忽略评论中提供尽可能多的细节,以便您知道何时让测试再次运行。如果您怀疑该测试不会以其当前形式再次使用,请将其删除——如果您错了,您始终可以从版本控制中检索它。

This strategy means that, since you are only automating the happy path, you will have to perform a correspondingly larger amount of manual testing to ensure that you system is working fully as it should. You should find that the manual tests change rapidly since they’ll be testing new or newly changed functionality. The moment you discover you are testing the same function manually more than a couple of times, check and see if that functionality is likely to change. If not, automate the test. Conversely, if you find you are spending a great deal of time fixing particular tests, you can assume that the functionality under test is changing. Again, go and check with the customer and development team if this is the case. If so, it is usually possible to tell your automated testing framework to ignore the test, remembering to give as much detail as possible in the ignore comment so that you know when to get the test working again. If you suspect the test won’t be used again in its present form, delete it—you can always retrieve it from version control if you’re wrong.

当您时间紧迫时,您将无法花费大量精力编写具有大量交互的复杂场景的脚本。在这种情况下最好使用多组测试数据,以保证覆盖率。明确指定您的测试目标,找到实现此目标的最简单的脚本,并根据测试开始时应用程序的状态尽可能多地补充它。我们在第 12 章“管理数据”中讨论了自动加载测试数据。

When you are pressed for time, you won’t be able to spend a great deal of effort on scripting complex scenarios with a lot of interactions. In this situation it’s better to use a variety of sets of test data in order to ensure coverage. Specify clearly the objective of your test, find the simplest possible script which fulfills this objective, and supplement it with as many scenarios as possible in terms of the state of the application at the beginning of the test. We discuss automating the loading of test data in Chapter 12, “Managing Data.”

遗留系统

Legacy Systems

迈克尔·费瑟斯 (Michael Feathers) 在他的《有效地使用遗留代码工作》( Working Effectively with Legacy Code ) 一书中挑衅性地将遗留系统定义为没有自动测试的系统。这是一个有用且简单(尽管有争议)的定义。随着这个简单的定义而来的是一个简单的经验法则:测试您更改的代码。

Michael Feathers, in his book Working Effectively with Legacy Code, provocatively defined legacy systems as systems that do not have automated tests. This is a useful and simple (although controversial) definition. Along with this simple definition comes a simple rule of thumb: Test the code that you change.

处理此类系统时的首要任务是创建一个自动化构建过程(如果不存在),然后围绕它创建一个自动化功能测试脚手架。如果文档可用,或者更好的是,在遗留系统上工作的团队成员可用,则创建自动化测试套件会更容易。然而,通常情况并非如此。

The first priority when dealing with such a system is to create an automated build process if one doesn’t exist, and then create an automated functional test scaffolding around it. Creating an automated test suite will be easier if documentation, or better still, members of the team who worked on the legacy system are available. However, this is often not the case.

通常,项目的发起人不愿意让开发团队花时间在他们看来是低价值的活动上——为已经投入生产的系统的行为创建测试:“这不是已经在QA 团队的过去?” 因此,以系统的高价值操作为目标很重要。很容易向客户解释创建回归测试套件以保护系统的这些功能的价值。

Often, the sponsors of the project are unwilling to allow the development team spend time on what seems to them a low-value activity—creating tests for the behavior of a system that is already in production: “Hasn’t this already been tested in the past by the QA team?” So it is important to target the high-value actions of the system. It is easy to explain to the customer the value of creating a regression test suite to protect these functions of the system.

与系统用户坐下来确定其高价值用途很重要。使用上一节中描述的相同技术,创建一组覆盖此核心高价值功能的广泛自动化测试。你不应该花太长时间这样做,因为这是保护遗留功能的骨架。您稍后将为您添加的新行为逐步添加新测试。这些本质上是对遗留系统的冒烟测试。

It is important to sit down with users of the system to identify its high-value uses. Using the same techniques described in the previous section, create a set of broad automated tests that cover this core high-value functionality. You shouldn’t spend too long doing this, since this is a skeleton to protect the legacy functions. You will be adding new tests incrementally later for the new behavior that you add. These are essentially smoke tests for your legacy system.

一旦这些冒烟测试就位,您就可以开始开发故事了。此时对您的自动化测试采用分层方法很有用。第一层应该是非常简单和快速运行的测试,以解决阻止您对您正在处理的任何功能进行有用测试和开发的问题。第二层测试特定故事的关键功能。应尽可能按照我们为新项目描述的相同方式开发和测试新行为。应该为新特性创建带有验收标准的故事,并且应该强制执行自动化测试来代表这些故事的完成。

Once these smoke tests are in place, you can begin development on stories. It is useful at this point to take a layered approach to your automated tests. The first layer should be very simple and fast-running tests for problems that prevent you from doing useful testing and development on whatever piece of functionality you’re working on. The second layer tests the critical functionality for a particular story. As much as possible, new behaviors should be developed and tested in the same way that we described for a new project. Stories with acceptance criteria should be created for the new features, and automated tests should be mandated to represent completion of these stories.

这有时比听起来更难。设计为可测试的系统往往比那些不可测试的系统更模块化,更容易测试。但是,这不应使您偏离目标。

This can sometimes be harder than it sounds. Systems designed to be testable tend to be more modular and easier to test than those that are not. However, this should not divert you from the goal.

这种遗留系统的一个特殊问题是代码通常不是太模块化和结构化。因此,改变其中的一部分是很常见的对另一个区域的行为产生不利影响的代码。在这种情况下,一种有用的策略是在测试完成时仔细验证应用程序的状态。如果你有时间,你可以测试故事的替代路径。最后,您可以编写更多的验收测试来检查异常情况或防止常见的故障模式或不良副作用。

A particular problem of such legacy systems is that the code is often not too modular and well structured. Thus it is common for a change in one part of the code to adversely affect behavior in another area. One useful strategy in such circumstances can be to include a careful validation of the state of the application at the completion of the test. If you have time, you can test the alternate paths of the story. Finally, you can write more acceptance tests checking for exception conditions or protecting against common failure modes or undesirable side effects.

重要的是要记住,您应该只在可以交付价值的地方编写自动化测试。您基本上可以将您的应用程序分为两部分。有实现应用程序功能的代码,下面有支持或框架代码。绝大多数回归错误是由更改框架代码引起的——因此,如果您只是向应用程序添加不需要更改框架和支持代码的功能,那么编写全面的脚手架就没有什么价值。

It is important to remember that you should only write automated tests where they will deliver value. You can essentially divide your application into two parts. There is the code that implements the features of your application, and there is the support or framework code underneath it. The vast majority of regression bugs are caused by altering framework code—so if you are only adding features to your application that do not require changes to the framework and support code, then there’s little value in writing a comprehensive scaffolding.

例外情况是您的软件必须在许多不同的环境中运行。在这种情况下,自动化测试与自动化部署相结合到类似生产的环境提供了巨大的价值,因为您可以简单地将脚本指向要测试的环境,并为您自己节省大量手动测试工作。

The exception to this is when your software has to run in a number of different environments. In this case, automated tests combined with automated deployment to production-like environments deliver a great deal of value since you can simply point your scripts at the environments to be tested and save yourself a lot of effort on manual testing.

集成测试

Integration Testing

如果您的应用程序通过一系列不同的协议与各种外部系统进行对话,或者如果您的应用程序本身由一系列松散耦合的模块组成,并且它们之间存在复杂的交互,那么集成测试就变得非常重要。集成测试和组件测试之间的界限是模糊的(尤其是因为集成测试是一个有点过载的术语)。我们使用术语集成测试来指代确保应用程序的每个独立部分与其所依赖的服务正常工作的测试。

If your application is conversing with a variety of external systems through a series of different protocols, or if your application itself consists of a series of loosely coupled modules with complex interactions between them, then integration tests become very important. The line between integration testing and component testing is blurry (not least because integration testing is a somewhat overloaded term). We use the term integration testing to refer to tests which ensure that each independent part of your application works correctly with the services it depends on.

集成测试可以像编写正常验收测试一样编写。通常,集成测试应该在两种情况下运行:首先,被测系统针对它所依赖的真实外部系统运行,或者针对它们由服务提供商控制的副本运行,其次针对您作为代码库的一部分创建的测试工具.

Integration tests can be written in the same way as you write normal acceptance tests. Normally, integration tests should run in two contexts: firstly with the system under test running against the real external systems it depends on, or against their replicas controlled by the service provider, and secondly against a test harness which you create as part of your codebase.

除非您在生产中,否则必须确保您不会攻击真正的外部系统,或者您有某种方式告诉服务您正在向其发送虚拟交易以用于测试目的。有两种常见的方法可以确保您可以安全地测试您的应用程序而不会影响真正的外部系统,通常您需要同时使用这两种方法:

It is essential to ensure that you don’t hit a real external system unless you are in production, or you have some way of telling the service that you are sending it dummy transactions for testing purposes. There are two common ways to ensure that you can safely test your application without hitting a real external system, and generally you will need to employ both of them:

• 在您的测试环境中使用防火墙隔离对外部系统的访问,这在任何情况下您都可能希望在您的开发过程的早期进行。这也是在外部服务不可用时测试应用程序行为的有用技术。

• Isolate access to the external system in your testing environment with a firewall, which you probably want to do in any case early on in your development process. This is also a useful technique to test the behavior of your application when the external service is unavailable.

• 在您的应用程序中进行配置设置,使其与外部系统的模拟版本对话。

• Have a configuration setting in your application that makes it talk to a simulated version of the external system.

在理想情况下,服务提供商将拥有一个行为与生产服务完全相同的副本测试服务,除了其性能特征。您可以针对此开发测试。然而,在现实世界中,您通常需要开发自己的测试工具。这是以下情况:

In an ideal situation, the service provider will have a replica test service that behaves exactly like the production service, except in terms of its performance characteristics. You can develop your tests against this. However, in the real world, you will often need to develop a test harness of your own. This is the case when:

• 外部系统正在开发中,但接口已提前定义(在这些情况下,请为接口更改做好准备)。

• The external system is under development but the interface has been defined ahead of time (in these situations, be prepared for the interface to change).

• 外部系统已经开发,但您没有该系统的测试实例可用于您的测试,或者测试系统太慢或有缺陷,无法充当常规自动测试运行的服务。

• The external system is developed already but you don’t have a test instance of that system available for your testing, or the test system is too slow or buggy to act as a service for regular automated test runs.

• 存在测试系统,但响应不是确定性的,因此无法对自动化测试(例如,股票市场提要)进行测试结果验证。

• The test system exists, but responses are not deterministic, and so make validation of tests results impossible for automated tests (for example, a stock market feed).

• 外部系统采用难以安装或需要通过用户界面进行手动干预的另一个应用程序的形式。

• The external system takes the form of another application that is difficult to install or requires manual intervention via a UI.

• 您需要为涉及外部服务的功能编写标准的自动验收测试。这些应该几乎总是与测试替身对抗。

• You need to write standard automated acceptance tests for functionality involving external services. These should almost always run against test doubles.

• 您的自动化持续集成系统施加的负载及其所需的服务级别压倒了仅用于应对少数手动探索性交互的轻量级测试环境。

• The load that your automated continuous integration system imposes, and the service level that it requires, overwhelms the lightweight test environment that is only set up to cope with a few manual exploratory interactions.

测试工具可能非常复杂,尤其取决于它加倍的服务是否记住状态。如果外部系统记住状态,您的线束将根据您发送的请求表现不同。在这种情况下,您可以编写的最高价值测试是黑盒测试,您可以在其中考虑外部系统可以给出的所有可能响应,并为这些响应中的每一个编写测试。您的模拟外部系统需要某种方式来识别您的请求并发回适当的响应,或者如果它收到它不期望的请求则抛出一个异常。

Test harnesses can be quite sophisticated, depending, in particular, on whether the service it doubles up for remembers state or not. If the external system remembers state, your harness will behave differently according to the requests that you send. The highest-value tests that you can write in this situation are black box tests, in which you consider all the possible responses your external system can give and write a test for each of these responses. Your mock external system needs some way of identifying your request and sending back the appropriate response, or an exception if it gets a request it’s not expecting.

至关重要的是,您的测试工具不仅要复制对服务调用的预期响应,还要复制意外响应。释放它!, Michael Nygard 讨论了创建一个测试工具,它模拟了远程系统出错或基础设施问题可能导致的各种有害行为。3个 这些行为可能是由于网络传输问题、网络协议问题、应用程序协议问题和应用程序逻辑问题引起的。示例包括拒绝网络连接、接受然后丢弃、接受连接但不回复、请求响应极慢、返回意外大量数据、回复垃圾、拒绝凭据、发送异常或回复等病态现象给定应用程序的状态,格式正确的响应无效。您的测试工具应该能够模拟这些条件中的每一个,也许通过监听几个不同的端口,每个端口对应于一些故障模式。

It is essential that your test harness replicates not only the expected responses to service calls, but also unexpected ones. In Release It!, Michael Nygard discusses creating a test harness which simulates the kinds of pernicious behavior you can expect from remote systems that go wrong or from infrastructural problems.3 These behaviors could be due to network transport problems, network protocol problems, application protocol problems, and application logic problems. Examples include such pathological phenomena as refusing network connections, accepting them and then dropping them, accepting connections but never replying, responding extremely slowly to requests, sending back unexpectedly large amounts of data, replying with garbage, refusing credentials, sending back exceptions, or replying with a well-formed response that is invalid given the state of the application. Your test harness should be able to simulate each of these conditions, perhaps by listening on several different ports, each of which corresponds to some failure mode.

您应该针对尽可能多的病态情况测试您的应用程序,以确保它能够处理它们。Nygard 描述的其他模式(例如断路器和隔板)随后可用于强化您的应用程序,以应对生产中必然会发生的各种意外事件。

You should test your application against as many pathological situations as you can simulate to make sure it can handle them. That other patterns the Nygard describes, such as Circuit Breaker and Bulkheads, can then be used to harden your application against the kinds of unexpected events that are bound to occur in production.

在将系统部署到生产环境期间,自动化集成测试可以作为冒烟测试重复使用。它们也可以用作诊断来监控生产系统。如果您将集成问题确定为开发过程中的风险(这几乎是不可避免的),那么开发自动化集成测试应该是早期的优先事项。

Automated integration tests can be reused as smoke tests during deployment of your system into production. They can also be used as diagnostics to monitor the production system. If you identify integration problems as a risk during development, which they almost inevitably are, developing automated integration tests should be an early priority.

必须将有关集成的活动纳入您的发布计划。与外部服务集成很复杂,需要时间和计划。每次必须与外部系统集成时,都会给项目增加风险:

It is essential to incorporate activities concerning integration into your release plan. Integrating with external services is complex and requires time and planning. Every time you have to integrate with an external system, you add risks to your project:

• 是否提供测试服务,它是否运行良好?

• Will a test service be available, and will it perform well?

• 服务提供商是否有带宽来回答问题、修复错误和添加自定义功能?

• Do the providers of the service have bandwidth to answer questions, fix bugs, and add custom functionality?

• 我能否访问系统的生产版本,我可以对其进行测试以诊断容量或可用性问题?

• Will I have access to a production version of the system that I can test against to diagnose capacity or availability problems?

• 是否可以使用开发我的应用程序所用的技术轻松访问服务API,或者我们是否需要团队的专业技能?

• Is the service API accessible easily using the technology my application is developed with, or will we need specialist skills on the team?

• 我们是否必须编写和维护我们自己的测试服务?

• Are we going to have to write and maintain our own test service?

• 当外部服务未按预期运行时,我的应用程序将如何执行?

• How will my application perform when the external service doesn’t behave as expected?

此外,您还必须添加用于构建和维护集成层和相关运行时配置的范围,以及所需的任何测试服务和测试策略(如容量测试)。

In addition, you will have to add scope for building and maintaining the integration layer and the associated runtime configuration, as well as any test services required and testing strategies such as capacity testing.

过程

Process

如果团队成员之间的沟通不畅,验收测试的制作可能是一项昂贵且费力的任务。许多项目依赖于测试人员详细检查即将到来的需求,检查所有可能的场景,并设计他们稍后将遵循的复杂测试脚本。此过程的结果可能会发送给客户以供批准,然后实施测试。

The production of acceptance tests can be an expensive and even laborious task if communication between the team members isn’t effective. Many projects rely on testers examining upcoming requirements in detail, going through all possible scenarios, and designing complex test scripts they will follow later. The results of this process might be sent to the customer for approval, following which the tests are implemented.

有几个点可以非常简单地优化此过程。我们发现最好的解决方案是在每次迭代开始时与所有利益相关者举行一次会议,或者如果您不使用迭代,则在故事开始开发前大约一周。我们将客户、分析师和测试人员聚集在一个房间里,提出最高优先级的场景进行测试。Cucumber、JBehave、Concordion 和 Twist 等工具允许您在文本编辑器中以自然语言写下验收标准,然后编写代码使这些测试可执行。重构测试代码也会更新测试规范。另一种方法是使用领域特定语言 (DSL) 进行测试。这允许在 DSL 中输入验收标准。至少,我们将要求客户编写尽可能简单的验收测试,涵盖这些场景的快乐路径。后来,在这次会议之后,人们通常会添加更多的数据集来提高测试的覆盖率。

There are several points at which this process can be very simply optimized. We find that the best solution is to have a single meeting with all of the stakeholders at the beginning of each iteration, or about a week before a story will start development if you’re not using iterations. We get customers, analysts, and testers in a room together and come up with the highest-priority scenarios to test. Tools like Cucumber, JBehave, Concordion, and Twist allow you to write acceptance criteria down in natural language in a text editor and then write code to make these tests executable. Refactorings to the test code also update the test specifications. Another approach is to use a domain-specific language (DSL) for testing. This allows acceptance criteria to be entered in the DSL. As a minimum, we will ask the customers to write the simplest possible acceptance tests covering the happy paths of these scenarios there and then. Later, after this meeting, people will often add more sets of data to use to improve the coverage of the tests.

这些验收测试及其目标的简短描述随后成为开发人员处理相关故事的起点。测试人员和开发人员应该在开始开发之前尽早聚在一起讨论验收测试。这使开发人员可以很好地了解故事并了解最重要的场景是什么。这减少了开发人员和测试人员之间的反馈周期,否则可能会在故事开发结束时发生,并有助于减少错过的功能和错误的数量。

These acceptance tests, and the short descriptions of their objectives, then become the starting point for developers working on the stories concerned. Testers and developers should get together as early as possible to discuss the acceptance tests before starting development. This allows developers to get a good overview of the story and understand what the most important scenarios are. This reduces the feedback cycle between developers and testers that can otherwise occur at the end of development of a story and helps reduce both missed functionality and the number of bugs.

故事结束时开发人员和测试人员之间的交接过程很容易成为瓶颈。在最坏的情况下,开发人员可以完成一个故事,开始另一个故事,并在新故事进行到一半时被测试人员打断,测试人员在之前的故事(甚至是前一段时间完成的故事)中提出了错误。这是非常低效的。

The handover process between developers and testers at the end of the story can easily become a bottleneck. In the worst case, developers can finish a story, begin on another story, and be interrupted halfway through the new story by a tester who has raised bugs on the previous story (or even a story that was completed some time ago). This is very inefficient.

在故事的整个开发过程中,开发人员和测试人员之间的密切协作对于顺利发布是必不可少的。每当开发人员完成某些功能时,他们应该召集测试人员进行审查。测试人员应该接管开发人员的机器来做这个测试。在此期间,开发人员可能会继续在相邻的终端或笔记本电脑上工作,可能会修复一些突出的回归错误。这样他们仍然有空(因为测试可能需要一些时间),但在测试人员需要讨论任何事情时很容易找到。

Close collaboration between developers and testers throughout the development of a story is essential to a smooth path to the release. Whenever developers finish some functionality, they should call over the testers to review it. The testers should take over the developers’ machine to do this testing. During this time, developers might continue work on an adjacent terminal or laptop, perhaps fixing some outstanding regression bugs. This way they’re still occupied (since testing can take some time), but are easily available in case the tester needs to discuss anything.

管理缺陷积压

Managing Defect Backlogs

理想情况下,从一开始就不应将错误引入您的应用程序。如果您正在实践测试驱动开发和持续集成,并且拥有一套全面的自动化测试,包括系统级别的验收测试以及单元和组件测试,开发人员应该能够在测试人员或用户发现错误之前发现错误。但是,探索性测试、展示和用户不可避免地会发现您系统中的错误。这些错误通常会在缺陷积压中结束。

Ideally, bugs should never be introduced into your application in the first place. If you are practicing test-driven development and continuous integration and have a comprehensive set of automated tests including acceptance tests at the system level as well as unit and component tests, developers should be able to catch bugs before they are discovered by testers or users. However, exploratory testing, showcases, and users will inevitably discover bugs in your system. These bugs will typically end up in a defect backlog.

关于什么是可接受的缺陷积压以及如何解决它,存在多种思想流派。James Shore 提倡零缺陷 [b3m55V]。实现此目的的一种方法是确保无论何时发现错误,都立即修复。这当然需要您的团队以这样一种方式构建,即测试人员可以及早发现错误,而开发人员可以立即修复它们。但是,如果您已经有缺陷积压,这将无济于事。

There are several schools of thought on what constitutes an acceptable defect backlog and how to address it. James Shore advocates having zero defects [b3m55V]. One way to achieve this is to ensure that whenever a bug is found, it is immediately fixed. This of course requires your team to be structured in such a way that testers can find bugs early, and developers can fix them straight away. However, this is not going to help if you already have a defect backlog.

如果存在错误积压,重要的是让每个人都清楚地看到问题,并且开发团队的成员要负责促进减少积压的过程。特别是,如果验收构建总是失败,则将其状态显示为“通过”或“失败”是不够的。相反,显示通过的测试数量、失败的数量和忽略的数量,并在显眼的地方张贴这些数字随时间变化的图表。这将团队的注意力集中在问题上。

Where a backlog of bugs exists, it is important for the problem to be clearly visible to everyone, and for members of the development team to be responsible for facilitating the process of reducing the backlog. In particular, having the status of your acceptance build displayed as “passed” or “failed” is not good enough if it is always failing. Instead, display the number of tests passed, the number failed, and the number ignored, and put up a graph of these numbers over time somewhere prominent. This focuses the team’s attention on the problem.

您决定继续处理积压的缺陷的场景是有风险的。这是一个滑坡。过去,许多开发团队和开发流程忽略了大量错误,将修复这些错误的工作推迟到将来更方便的时间。几个月后,这几乎不可避免地会导致大量的错误,其中一些永远不会被修复,一些因为应用程序的功能已经改变而不再相关,还有一些对某些用户来说很重要但已经丢失了所有的噪音。

The scenarios where you decide to continue with a backlog of defects are risky. This is a slippery slope. Many development teams and development processes in the past ignored significant numbers of bugs, deferring the effort to fix them to some more convenient time in the future. After a few months, this almost inevitably leads to a huge list of bugs, of which some will never be fixed, some are no longer relevant since the functionality of the application has changed, and some are critical to some user but have been lost in all the noise.

当没有验收测试或验收测试无效时,问题会更糟,因为功能是在没有定期合并到主干的分支上开发的。在这种情况下,一旦代码被集成并开始手动系统级测试,团队就会被缺陷完全淹没,这太常见了。测试人员、开发人员和管理人员之间爆发争论,发布日期推迟,用户得到质量低劣的软件。在这种情况下,可以通过遵循更好的流程来避免许多缺陷。有关详细信息,请参阅第 14 章“高级版本控制”。

The problem is even worse when there are no acceptance tests or where acceptance tests are not effective because features are being developed on branches that are not merged regularly to trunk. In this case, it is all too common, once the code is integrated and manual system-level testing starts, for teams to become completely overwhelmed by defects. Arguments break out between testers, developers, and management, release dates slip, and users get landed with poor-quality software. This is a case where many defects could have been prevented by following a better process. See Chapter 14, “Advanced Version Control,” for more details.

另一种方法是像对待特征一样对待缺陷。毕竟,处理错误比处理其他功能要花费时间和精力,因此由客户来确定特定错误相对于该功能的相对重要性的优先级。例如,在只有几个用户的管理屏幕中使用已知变通方法修复的罕见缺陷可能不如作为整个应用程序的新创收功能那么重要。至少,将错误分类为“严重”、“障碍”、“中等”和“低”优先级是有意义的。更全面的方法可能会考虑错误发生的频率、它对用户的影响以及是否有解决方法。

Another approach is to treat defects the same way as features. After all, working on a bug takes time and effort away from working on some other feature, so it is up to the customer to prioritize the relative importance of a particular bug against that feature. For example, a rare defect with a known workaround in an administrative screen with only a couple of users may not be so important to fix as a new revenue-generating feature for the application as a whole. At the very least, it makes sense to classify bugs as “critical,” “blockers,” “medium,” and “low” priority. A more comprehensive approach might take account of how often the bug occurs, what its effect on the user is, and if there is a workaround.

鉴于这种分类,错误可以在积压工作中按照与故事相同的方式进行优先排序,并且它们可以一起出现。除了立即消除关于某项工作是缺陷还是功能的争论之外,这还意味着您可以一目了然地看到还有多少工作需要完成,并相应地确定优先级。低优先级的错误会在你的待办事项列表中出现,你可以像对待低优先级的故事一样对待它们。通常情况下,客户宁愿不修复一些错误——因此在积压工作中将错误与特性一起是管理它们的合乎逻辑的方法。

Given this classification, bugs can be prioritized in your backlog in the same way as stories, and they can appear together. Apart from immediately removing arguments about whether a particular piece of work is a defect or a feature, it means you can see at a glance exactly how much work remains to be done and prioritize it accordingly. Low-priority bugs will go way back in your backlog, and you can treat them the same way you would treat a low-priority story. It is often the case that customers would rather not fix some bugs—so having bugs in the backlog along with features is a logical way to manage them.

概括

Summary

在许多项目中,测试被视为由专家执行的不同阶段。然而,高质量的软件只有在测试成为参与交付软件的每个人的责任并且从项目开始到整个生命周期都得到实践的情况下才有可能。测试主要关注建立驱动开发、设计和发布的反馈循环。任何将测试推迟到项目结束的计划都被打破了,因为它消除了产生更高质量、更高生产率的反馈循环,最重要的是,消除了衡量项目完成程度的任何措施。

In many projects, testing is treated as a distinct phase carried out by specialists. However, high-quality software is only possible if testing becomes the responsibility of everybody involved in delivering software and is practiced right from the beginning of the project and throughout its life. Testing is primarily concerned with establishing feedback loops that drive development, design, and release. Any plan that defers testing to the end of the project is broken because it removes the feedback loop that generates higher quality, higher productivity, and, most importantly of all, any measure of how complete the project is.

最短的反馈循环是通过在每次系统更改时运行的自动化测试集创建的。此类测试应在所有级别运行——从单元测试到验收测试(功能性和非功能性)。自动化测试应辅以手动测试,例如探索性测试和展示。本章旨在让您很好地理解创建出色反馈所需的各种类型的自动化和手动测试,以及如何在各种类型的项目中实施它们。

The shortest feedback loops are created through sets of automated tests that are run upon every change to the system. Such tests should run at all levels—from unit tests up to acceptance tests (both functional and nonfunctional). Automated tests should be supplemented with manual testing such as exploratory testing and showcases. This chapter aims to give you a good understanding of the various types of automated and manual tests required to create excellent feedback and how to implement them on various types of projects.

第 83 页“简介”部分描述的原则中,我们讨论了“完成”的定义。将测试纳入交付过程的每个部分对于完成工作至关重要。由于我们的测试方法定义了我们对“完成”的理解,因此测试结果是项目规划的基石。

In the principles that we described in the “Introduction” section on page 83, we discuss what defines “done.” Incorporating testing into every part of your delivery process is vital to getting work done. Since our approach to testing defines our understanding of “done,” the results of testing are the cornerstone of project planning.

测试从根本上与您对“完成”的定义相互关联,您的测试策略应侧重于能够逐个提供对功能的理解,并确保测试在整个过程中无处不在。

Testing is fundamentally interconnected with your definition of “done,” and your testing strategy should be focused on being able to deliver that understanding feature by feature and ensuring that testing is pervasive throughout your process.

第二部分:部署管道

Part II: The Deployment Pipeline

第 5 章部署流水线剖析

Chapter 5. Anatomy of the Deployment Pipeline

介绍

Introduction

对于大多数采用它的项目来说,持续集成是生产力和质量的巨大进步。它确保团队合作创建大型和复杂的系统可以比没有它时更有信心和控制力。CI 确保我们作为一个团队创建的代码能够正常工作,方法是为我们提供有关我们提交的更改可能引入的任何问题的快速反馈。它主要侧重于断言代码编译成功并通过了单元测试和验收测试。但是,CI 还不够。

Continuous integration is an enormous step forward in productivity and quality for most projects that adopt it. It ensures that teams working together to create large and complex systems can do so with a higher level of confidence and control than is achievable without it. CI ensures that the code that we create, as a team, works by providing us with rapid feedback on any problems that we may introduce with the changes we commit. It is primarily focused on asserting that the code compiles successfully and passes a body of unit and acceptance tests. However, CI is not enough.

CI主要关注开发团队。CI 系统的输出通常形成手动测试过程的输入,然后形成发布过程的其余部分。发布软件的大部分浪费来自软件通过测试和操作的进展。例如,经常看到

CI mainly focuses on development teams. The output of the CI system normally forms the input to the manual testing process and thence to the rest of the release process. Much of the waste in releasing software comes from the progress of software through testing and operations. For example, it is common to see

• 构建和运营团队等待文档或修复

• Build and operations teams waiting for documentation or fixes

• 等待软件“良好”构建的测试人员

• Testers waiting for “good” builds of the software

• 开发团队在转向新功能几周后收到错误报告

• Development teams receiving bug reports weeks after the team has moved on to new functionality

• 在开发过程快要结束时发现应用程序的架构不支持系统的非功能性需求

• Discovering, towards the end of the development process, that the application’s architecture will not support the system’s nonfunctional requirements

这会导致软件无法部署,因为它需要很长时间才能进入类似生产的环境,并导致错误,因为开发团队与测试和运营团队之间的反馈周期太长了。

This leads to software that is undeployable because it has taken so long to get it into a production-like environment, and buggy because the feedback cycle between the development team and the testing and operations team is so long.

软件交付方式有各种渐进式改进,这将产生立竿见影的好处,例如教开发人员编写生产就绪软件、在类似生产的系统上运行 CI 以及建立跨职能团队。然而,虽然像这样的做法肯定会改善重要的是,它们仍然无法让您深入了解交付过程中的瓶颈在哪里或如何针对它们进行优化。

There are various incremental improvements to the way software is delivered which will yield immediate benefits, such as teaching developers to write production-ready software, running CI on production-like systems, and instituting cross-functional teams. However, while practices like these will certainly improve matters, they still don’t give you an insight into where the bottlenecks are in the delivery process or how to optimize for them.

解决方案是采用更全面、端到端的方法来交付软件。我们已经解决了配置管理和自动化大量构建、部署、测试和发布过程的更广泛问题。我们已经采取这一点,部署我们的应用程序,甚至是生产,通常只需单击一个按钮来选择我们希望部署的构建即可完成。这创建了一个强大的反馈循环:由于将应用程序部署到测试环境非常简单,您的团队可以快速获得有关代码和部署过程的反馈。由于部署过程(无论是开发机器还是最终发布)是自动化的,它会运行并因此定期测试,从而降低发布风险并将部署过程的知识转移给开发团队。

The solution is to adopt a more holistic, end-to-end approach to delivering software. We have addressed the broader issues of configuration management and automating large swathes of our build, deploy, test, and release processes. We have taken this to the point where deploying our applications, even to production, is often done by a simple click of a button to select the build that we wish to deploy. This creates a powerful feedback loop: Since it’s so simple to deploy your application to testing environments, your team gets rapid feedback on both the code and the deployment process. Since the deployment process (whether to a development machine or for final release) is automated, it gets run and therefore tested regularly, lowering the risk of a release and transferring knowledge of the deployment process to the development team.

我们最终得到的是(用精益的说法)一个拉式系统测试团队只需按一下按钮,即可自行将构建部署到测试环境中。只需按一下按钮,运维人员就可以将构建部署到暂存和生产环境中。开发人员可以看到哪些版本在发布过程中经历了哪些阶段,以及发现了哪些问题。管理人员可以关注周期时间、吞吐量和代码质量等关键指标。结果,交付过程中的每个人都得到了两件事:在需要时访问他们需要的东西,以及对发布过程的可见性以改进反馈,从而识别、优化和消除瓶颈。这导致交付过程不仅更快而且更安全。

What we end up with is (in lean parlance) a pull system. Testing teams deploy builds into testing environments themselves, at the push of a button. Operations can deploy builds into staging and production environments at the push of a button. Developers can see which builds have been through which stages in the release process, and what problems were found. Managers can watch such key metrics as cycle time, throughput, and code quality. As a result, everybody in the delivery process gets two things: access to the things they need when they need them, and visibility into the release process to improve feedback so that bottlenecks can be identified, optimized, and removed. This leads to a delivery process which is not only faster but also safer.

我们的构建、部署、测试和发布过程的端到端自动化的实施产生了许多连锁反应,带来了一些意想不到的好处。这样的结果之一是,在使用此类技术的许多项目的过程中,我们已经确定了我们构建的部署管道系统之间的许多共同点。我们相信,通过我们已经确定的抽象,到目前为止,一些通用模式已经适合我们尝试过的所有项目。这种理解使我们能够从项目一开始就非常快速地启动和运行相当复杂的构建、测试和部署系统。这些端到端的部署管道系统意味着我们在交付项目中体验到了一定程度的自由度和灵活性,这在几年前是难以想象的。

The implementation of end-to-end automation of our build, deploy, test, and release processes has had a number of knock-on effects, bringing some unexpected benefits. One such outcome is that over the course of many projects utilizing such techniques, we have identified much in common between the deployment pipeline systems that we have built. We believe that with the abstractions we have identified, some general patterns have, so far, fit all of the projects in which we have tried them. This understanding has allowed us to get fairly sophisticated build, test, and deployment systems up and running very quickly from the start of our projects. These end-to-end deployment pipeline systems have meant that we have experienced a degree of freedom and flexibility in our delivery projects that would have been hard to imagine a few years ago. We are convinced that this approach has allowed us to create, test, and deploy complex systems of higher quality and at significantly lower cost and risk than we could otherwise have done.

这就是部署管道的用途。

This is what the deployment pipeline is for.

什么是部署管道?

What Is a Deployment Pipeline?

在抽象层面上,部署管道是您将软件从版本控制系统交付到用户手中的过程的自动化体现。对软件的每一次更改都会经历一个复杂的过程被释放。该过程涉及构建软件,然后是这些构建通过测试和部署的多个阶段取得的进展。反过来,这需要许多人,也许还有几个团队之间的协作。部署管道对这个过程进行建模,它在持续集成和发布管理工具中的化身使您能够查看和控制每个更改的进度,因为它从版本控制通过各种测试和部署集移动到向用户发布。

At an abstract level, a deployment pipeline is an automated manifestation of your process for getting software from version control into the hands of your users. Every change to your software goes through a complex process on its way to being released. That process involves building the software, followed by the progress of these builds through multiple stages of testing and deployment. This, in turn, requires collaboration between many individuals, and perhaps several teams. The deployment pipeline models this process, and its incarnation in a continuous integration and release management tool is what allows you to see and control the progress of each change as it moves from version control through various sets of tests and deployments to release to users.

图 5.1 产品的简单价值流图

Figure 5.1 A simple value stream map for a product

图片

因此,由部署管道建模的过程,即获取软件从签入到发布的过程,构成了将功能从客户或用户的头脑中获取到他们手中的过程的一部分。整个过程——从概念到现金——可以建模为价值流图。图 5.1显示了用于创建新产品的高级价值流图

Thus the process modeled by the deployment pipeline, the process of getting software from check-in to release, forms a part of the process of getting a feature from the mind of a customer or user into their hands. The entire process—from concept to cash—can be modeled as a value stream map. A high-level value stream map for the creation of a new product is shown in Figure 5.1.

这张价值流图讲述了一个故事。整个过程大约需要三个半月。其中大约两个半月用于实际工作——在将软件从概念变为现金的过程中,各个阶段之间都有等待。例如,在开发团队完成第一个版本的工作和测试过程开始之间有五天的等待时间。例如,这可能是由于将应用程序部署到类似生产的环境所花费的时间。顺便说一句,此图中故意不清楚该产品是否正在以迭代方式开发。在迭代过程中,您会期望看到开发过程本身由多个迭代组成,其中包括测试和展示。1个

This value stream map tells a story. The whole process takes about three and a half months. About two and a half months of that is actual work being done—there are waits between the various stages in the process of getting the software from concept to cash. For example, there is a five-day wait between the development team completing work on the first release and the start of the testing process. This might be due to the time it takes to deploy the application to a production-like environment, for example. As an aside, it has been left deliberately unclear in this diagram whether or not this product is being developed in an iterative way. In an iterative process, you’d expect to see the development process itself consist of several iterations which include testing and showcasing. The whole process from discovery to release would also be repeated many times1

创建价值流图可能是一个技术含量低的过程。在 Mary 和 Tom Poppendieck 的经典著作Lean Software Development: An Agile Toolkit中,他们描述如下。

Creating a value stream map can be a low-tech process. In Mary and Tom Poppendieck’s classic, Lean Software Development: An Agile Toolkit, they describe it as follows.

手拿铅笔和便笺簿,前往组织收到客户请求的地方。您的目标是绘制从到达到完成的平均客户请求图表。与参与每项活动的人员合作,您可以勾勒出满足请求所需的所有流程步骤,以及请求在每个步骤中花费的平均时间。在地图的底部,绘制一条时间线,显示请求在增值活动中花费的时间以及等待状态和非增值活动的时间。

With a pencil and pad in hand, go to the place where a customer request comes into your organization. You goal is to draw a chart of the average customer request, from arrival to completion. Working with the people involved in each activity, you sketch all the process steps necessary to fill the request, as well as the average amount of time that a request spends in each step. At the bottom of the map, draw a timeline that shows how much time the request spends in value-adding activities and how much in waiting states and non-value-adding activities.

如果你有兴趣做一些组织转型工作来改进流程,你需要更详细地描述谁负责流程的哪一部分,在特殊情况下发生哪些子流程,谁批准交接,需要什么资源,组织报告结构是什么,等等。然而,这对于我们在这里的讨论来说不是必需的。有关这方面的更多详细信息,请参阅 Mary 和 Tom Poppendieck 的书实施精益软件开发:从概念到现金

If you were interested in doing some organizational transformation work to improve the process, you would need to go into even more detail and describe who is responsible for which part of the process, what subprocesses occur in exceptional conditions, who approves the hand-offs, what resources are required, what the organizational reporting structures are, and so forth. However, that’s not necessary for our discussion here. For more details on this, consult Mary and Tom Poppendieck’s book Implementing Lean Software Development: From Concept to Cash.

图 5.2 通过部署管道进行的更改

Figure 5.2 Changes moving through the deployment pipeline

图片

我们在本书中讨论的价值流部分是从开发到发布的那部分。这些是图 5.1中价值流中的阴影框。这部分价值流的一个关键区别在于,构建在发布的过程中会多次通过它。事实上,了解部署管道以及更改如何通过它的一种方法是将其可视化为序列图,2如图5.2所示

The part of the value stream we discuss in this book is the one that goes from development through to release. These are the shaded boxes in the value stream in Figure 5.1. One key difference of this part of the value stream is that builds pass through it many times on their way to release. In fact, one way to understand the deployment pipeline and how changes move through it is to visualize it as a sequence diagram,2 as shown in Figure 5.2.

请注意,管道的输入是版本控制中的特定修订。每一个变化都会创建一个构建,就像一些神话英雄一样,通过一系列测试和挑战,使其作为生产版本的可行性。这个一系列测试阶段的过程,每个阶段都从不同的角度评估构建,从每次提交到版本控制系统开始,就像持续集成过程的启动一样。

Notice that the input to the pipeline is a particular revision in version control. Every change creates a build that will, rather like some mythical hero, pass through a sequence of tests of, and challenges to, its viability as a production release. This process of a sequence of test stages, each evaluating the build from a different perspective, is begun with every commit to the version control system, in the same way as the initiation of a continuous integration process.

图 5.3 部署管道中的权衡

Figure 5.3 Trade-offs in the deployment pipeline

图片

随着构建通过其适用性的每项测试,对它的信心也会增加。因此,我们愿意花费在它上面的资源增加了,这意味着构建所经过的环境变得越来越像生产环境。目标是尽早在流程中消除不合适的候选发布版本,并尽快向团队反馈失败的根本原因。为此,任何在过程中一个阶段失败的构建通常不会被提升到下一个阶段。这些权衡如图 5.3所示。

As the build passes each test of its fitness, confidence in it increases. Therefore, the resources that we are willing to expend on it increase, which means that the environments the build passes through become progressively more production-like. The objective is to eliminate unfit release candidates as early in the process as we can and get feedback on the root cause of failure to the team as rapidly as possible. To this end, any build that fails a stage in the process will not generally be promoted to the next. These trade-offs are shown in Figure 5.3.

应用这种模式有一些重要的后果。首先,您被有效地阻止发布到未经彻底测试并被发现适合其预期目的的生产版本中。避免了回归错误,尤其是在需要将紧急修复发布到生产环境中的情况下(此类修复与任何其他更改经历相同的过程)。根据我们的经验,新发布的软件由于系统组件与其环境之间的一些不可预见的相互作用而崩溃也是非常常见的,例如由于新的网络拓扑或生产服务器的配置。部署管道的规则减轻了这一点。

There are some important consequences of applying this pattern. First, you are effectively prevented from releasing into production builds that are not thoroughly tested and found to be fit for their intended purpose. Regression bugs are avoided, especially where urgent fixes need releasing into production (such fixes go through the same process as any other change). In our experience, it is also extremely common for newly released software to break down due to some unforeseen interaction between the components of the system and its environment, for example due to a new network topology or a slight difference in the configuration of a production server. The discipline of the deployment pipeline mitigates this.

其次,当部署和生产发布本身是自动化的时,它们是快速的、可重复的和可靠的。一旦流程自动化,执行发布通常会容易得多,以至于它们成为“正常”事件——这意味着,如果您选择,您可以更频繁地执行发布。在您既可以退回到早期版本又可以前进的情况下尤其如此。当此功能可用时,发布基本上没有风险。可能发生的最坏情况是您发现引入了一个严重的错误——此时您恢复到不包含该错误的早期版本,同时离线修复新版本(请参阅第 10 章,“部署和发布应用程序”)。

Second, when deployment and production release themselves are automated, they are rapid, repeatable, and reliable. It is often so much easier to perform a release once the process is automated that they become “normal” events—meaning that, should you choose, you can perform releases more frequently. This is particularly the case where you are able to step back to an earlier version as well as move forward. When this capability is available, releases are essentially without risk. The worst that can happen is that you find that you have introduced a critical bug—at which point you revert to an earlier version that doesn’t contain the bug while you fix the new release offline (see Chapter 10, “Deploying and Releasing Applications”).

为了达到这种令人羡慕的状态,我们必须自动化一套测试,以证明我们的候选版本符合他们的目的。我们还必须自动化部署到测试、暂存和生产环境,以删除这些手动密集型、容易出错的步骤。对于许多系统,还需要其他形式的测试以及发布过程中的其他阶段,但所有项目通用的子集如下。

To achieve this enviable state, we must automate a suite of tests that prove that our release candidates are fit for their purpose. We must also automate deployment to testing, staging, and production environments to remove these manually intensive, error-prone steps. For many systems, other forms of testing and so other stages in the release process are also needed, but the subset that is common to all projects is as follows.

提交阶段断言系统在技术层面工作。它编译、通过一套(主要是单元级的)自动化测试,并运行代码分析。

The commit stage asserts that the system works at the technical level. It compiles, passes a suite of (primarily unit-level) automated tests, and runs code analysis.

自动验收测试阶段断言系统在功能和非功能级别工作,在行为上满足用户的需求和客户的规范。

Automated acceptance test stages assert that the system works at the functional and nonfunctional level, that behaviorally it meets the needs of its users and the specifications of the customer.

手动测试阶段断言系统可用并满足其要求,检测自动测试未发现的任何缺陷,并验证它是否为用户提供了价值。这些阶段通常可能包括探索性测试环境、集成环境和 UAT(用户验收测试)。

Manual test stages assert that the system is usable and fulfills its requirements, detect any defects not caught by automated tests, and verify that it provides value to its users. These stages might typically include exploratory testing environments, integration environments, and UAT (user acceptance testing).

发布阶段将系统作为打包软件或通过将其部署到生产或暂存环境(暂存环境是与生产环境相同的测试环境)的方式交付给用户。

Release stage delivers the system to users, either as packaged software or by deploying it into a production or staging environment (a staging environment is a testing environment identical to the production environment).

我们将这些阶段,以及为交付软件的过程建模可能需要的任何其他阶段称为部署管道它有时也被称为持续集成管道、构建管道、部署生产线或实时构建。不管它叫什么,从根本上说,这是一个自动化的软件交付过程。这并不意味着在此发布过程中没有人与系统进行交互;相反,它确保容易出错和复杂的步骤在执行中是自动化的、可靠的和可重复的。事实上,增加了人机交互:通过按下按钮在系统开发的所有阶段部署系统的能力鼓励测试人员、分析师、开发人员和(最重要的)用户经常使用它。

We refer to these stages, and any additional ones that may be required to model your process for delivering software, as a deployment pipeline. It is also sometimes referred to as a continuous integration pipeline, a build pipeline, a deployment production line, or a living build. Whatever it is called, this is, fundamentally, an automated software delivery process. This is not intended to imply that there is no human interaction with the system through this release process; rather, it ensures that error-prone and complex steps are automated, reliable, and repeatable in execution. In fact, human interaction is increased: The ability to deploy the system at all stages of its development by pressing a button encourages its frequent use by testers, analysts, developers, and (most importantly) users.

基本部署管道

A Basic Deployment Pipeline

图 5.4 基本部署流水线

Figure 5.4 Basic deployment pipeline

图片

图 5.4显示了一个典型的部署管道并抓住了该方法的本质。当然,真实的流水线将反映您的项目交付软件的实际过程。

Figure 5.4 shows a typical deployment pipeline and captures the essence of the approach. Of course, a real pipeline will reflect your project’s actual process for delivering software.

该过程从开发人员将更改提交到他们的版本控制系统开始。此时,持续集成管理系统通过触发我们管道的新实例来响应提交。管道的第一个(提交)阶段编译代码、运行单元测试、执行代码分析并创建安装程序。如果单元测试全部通过并且代码符合要求,我们将可执行代码组装成二进制文件并将它们存储在工件存储库中。现代 CI 服务器提供了一种工具来存储这些工件,并使用户和管道中的后期阶段都可以轻松访问它们。或者,有很多工具可以帮助您管理工件,例如 Nexus 和 Artifactory。您还可以在管道的提交阶段运行其他任务,例如准备用于验收测试的测试数据库。现代 CI 服务器将允许您在构建网格上并行执行这些作业。

The process starts with the developers committing changes into their version control system. At this point, the continuous integration management system responds to the commit by triggering a new instance of our pipeline. The first (commit) stage of the pipeline compiles the code, runs unit tests, performs code analysis, and creates installers. If the unit tests all pass and the code is up to scratch, we assemble the executable code into binaries and store them in an artifact repository. Modern CI servers provide a facility to store artifacts like these and make them easily accessible both to the users and to the later stages in your pipeline. Alternatively, there are plenty of tools like Nexus and Artifactory which help you manage artifacts. There are other tasks that you might also run as part of the commit stage of your pipeline, such as preparing a test database to use for your acceptance tests. Modern CI servers will let you execute these jobs in parallel on a build grid.

第二阶段通常由运行时间较长的自动验收测试组成。同样,您的 CI 服务器应该让您将这些测试分成可以并行执行的套件,以提高它们的速度并更快地给您反馈——通常在一两个小时内。此阶段将在管道中的第一阶段成功完成时自动触发。

The second stage is typically composed of longer-running automated acceptance tests. Again, your CI server should let you split these tests into suites which can be executed in parallel to increase their speed and give you feedback faster—typically within an hour or two. This stage will be triggered automatically by the successful completion of the first stage in your pipeline.

此时,管道分支以支持将您的构建独立部署到各种环境——在本例中为 UAT(用户验收测试)、容量测试和生产。通常,您不希望成功完成验收测试阶段后自动触发这些阶段。相反,您会希望您的测试人员或运营团队能够手动将自助服务构建到他们的环境中。为此,您需要一个执行此部署的自动化脚本。您的测试人员应该能够看到可供他们使用的候选发布版本及其状态——每个构建已经通过了前两个阶段中的哪一个阶段,签入评论是什么,以及对这些构建的任何其他评论。

At this point, the pipeline branches to enable independent deployment of your build to various environments—in this case, UAT (user acceptance testing), capacity testing, and production. Often, you won’t want these stages to be automatically triggered by the successful completion of your acceptance test stage. Instead, you’ll want your testers or operations team to be able to self-service builds into their environments manually. To facilitate this, you’ll need an automated script that performs this deployment. Your testers should be able to see the release candidates available to them as well as their status—which of the previous two stages each build has passed, what were the check-in comments, and any other comments on those builds. They should then be able to press a button to deploy the selected build by running the deployment script in the relevant environment.

同样的原则适用于管道中的其他阶段,除了通常情况下,您希望能够部署到的各种环境将拥有不同的用户组,他们“拥有”这些环境并能够自助部署到它们. 例如,您的运营团队可能希望成为唯一可以批准部署到生产环境的人。

The same principle applies to further stages in the pipeline, except that, typically, the various environments you want to be able to deploy to will have different groups of users who “own” these environments and have the ability to self-service deployments to them. For example, your operations team will likely want to be the only one who can approve deployments to production.

图 5.5 Go 显示哪些更改已经通过了哪些阶段

Figure 5.5 Go showing which changes have passed which stages

图片

最后,重要的是要记住所有这一切的目的是尽快获得反馈。为了加快反馈周期,您需要能够查看将哪个构建部署到哪个环境中,以及每个构建已通过管道中的哪些阶段。图 5.5是 Go 的屏幕截图,显示了它在实践中的样子。

Finally, it’s important to remember that the purpose of all this is to get feedback as fast as possible. To make the feedback cycle fast, you need to be able to see which build is deployed into which environment, and which stages in your pipeline each build has passed. Figure 5.5 is a screenshot from Go showing what this looks like in practice.

请注意,您可以在页面下方看到每次签到、每次签到所经历的管道中的每个阶段,以及它是通过还是失败了该阶段。能够将特定的签入关联起来,从而将构建关联到它所经过的管道中的各个阶段,这一点至关重要。这意味着如果您在验收测试中发现问题(例如),您可以立即找出哪些更改已签入版本控制导致验收测试失败。

Notice that you can see every check-in down the side of the page, every stage in the pipeline that each check-in has been through, and whether it passed or failed that stage. Being able to correlate a particular check-in, and hence build, to the stages in the pipeline it has passed through is crucial. It means that if you see a problem in the acceptance tests (for example), you can immediately find out which changes were checked into version control that resulted in the acceptance tests failing.

部署管道实践

Deployment Pipeline Practices

很快,我们将详细介绍部署管道中的各个阶段。但在我们这样做之前,为了获得这种方法的好处,您应该遵循一些实践。

Shortly, we’ll go into some more detail on the stages in the deployment pipeline. But before we do so, in order to get the benefits of this approach, there are some practices you should follow.

只构建一次二进制文件

Only Build Your Binaries Once

为方便起见,我们将可执行代码的集合称为二进制文件,但如果您不需要编译代码,这些“二进制文件”可能只是源文件的集合。Jars、.NET 程序集和 .so 文件都是二进制文件的示例。

For convenience, we will refer to the collections of executable code as binaries, although if you don’t need to compile your code these “binaries” may be just collections of source files. Jars, .NET assemblies, and .so files are all examples of binaries.

许多构建系统使用版本控制系统中保存的源代码作为许多步骤的规范源代码。代码将在不同的上下文中重复编译:在提交过程中,在验收测试时再次编译,再次用于容量测试,并且通常为每个单独的部署目标编译一次。每次编译代码时,都会冒引入差异的风险。后期安装的编译器版本可能与您用于提交测试的版本不同。您可能会选择您不想要的某些第三方库的不同版本。甚至编译器的配置也可能改变应用程序的行为。我们已经看到这些来源中的每一个都在生产中出现了错误。

Many build systems use the source code held in the version control system as the canonical source for many steps. The code will be compiled repeatedly in different contexts: during the commit process, again at acceptance test time, again for capacity testing, and often once for each separate deployment target. Every time you compile the code, you run the risk of introducing some difference. The version of the compiler installed in the later stages may be different from the version that you used for your commit tests. You may pick up a different version of some third-party library that you didn’t intend. Even the configuration of the compiler may change the behavior of the application. We have seen bugs from every one of these sources reaching production.


图片

一个相关的反模式是在源代码级别而不是二进制级别进行升级。有关此反模式的更多信息,请参阅第 403 页的“ClearCase 和从源代码重建反模式”部分。

A related antipattern is to promote at the source-code level rather than at the binary level. For more information on this antipattern, see the “ClearCase and the Rebuilding-from-Source Antipattern” section on page 403.


这种反模式违反了两个重要原则。首先是保持部署管道的高效,以便团队尽快获得反馈。重新编译违反了这个原则,因为它需要时间,尤其是在大型系统中。第二个原则是始终建立在已知稳固的基础上。部署到生产环境中的二进制文件应该与通过验收测试过程的二进制文件完全相同——事实上,在许多管道实现中,这是通过在创建二进制文件时存储二进制文件的哈希值并验证二进制文件是正确的来检查的。在过程中的每个后续阶段都是相同的。

This antipattern violates two important principles. The first is to keep the deployment pipeline efficient, so the team gets feedback as soon as possible. Recompiling violates this principle because it takes time, especially in large systems. The second principle is to always build upon foundations known to be sound. The binaries that get deployed into production should be exactly the same as those that went through the acceptance test process—and indeed in many pipeline implementations, this is checked by storing hashes of the binaries at the time they are created and verifying that the binary is identical at every subsequent stage in the process.

如果我们重新创建二进制文件,我们将面临在创建二进制文件和发布二进制文件之间引入某些更改的风险,例如编译之间工具链的更改,并且我们发布的二进制文件将与我们发布的二进制文件不同测试。出于审计目的,必须确保在创建二进制文件和执行发布之间没有引入任何更改,无论是恶意的还是错误的。一些组织坚持编译和组装,或者解释语言的打包,发生在一个特殊的环境中,除了高级人员之外任何人都无法访问。一旦我们创建了二进制文件,我们将重用它们而无需在使用时重新创建它们。

If we re-create binaries, we run the risk that some change will be introduced between the creation of the binaries and their release, such as a change in the toolchain between compilations, and that the binary we release will be different from the one we tested. For auditing purposes, it is essential to ensure that no changes have been introduced, either maliciously or by mistake, between creating the binaries and performing the release. Some organizations insist that compilation and assembly, or packaging in the case of interpreted languages, occurs in a special environment that cannot be accessed by anyone except senior personnel. Once we have created our binaries, we will reuse them without re-creating them at the point of use.

因此,您应该只在构建的提交阶段构建一次二进制文件。这些二进制文件应该存储在文件系统中的某个地方(而不是在版本控制中,因为它们是您的基线的派生物,而不是其定义的一部分),以便在管道的后期阶段轻松检索它们。大多数 CI 服务器会为您处理这个,并且还会执行允许您追溯到用于创建它们的版本控制签入的关键任务。花费大量时间和精力确保备份二进制文件是不值得的——应该可以通过从版本控制中的正确修订运行自动构建过程来准确地重新创建它们。

So, you should only build your binaries once, during the commit stage of the build. These binaries should be stored on a filesystem somewhere (not in version control, since they are derivatives of your baseline, not part of its definition) where it is easy to retrieve them for later stages in the pipeline. Most CI servers will handle this for you, and will also perform the crucial task of allowing you to trace back to the version control check-in which was used to create them. It isn’t worth spending a lot of time and effort ensuring binaries are backed up—it should be possible to exactly re-create them by running your automated build process from the correct revision in version control.


图片

如果您接受我们的建议,最初会觉得您有更多的工作要做。如果您的 CI 工具尚未为您执行此操作,您将需要建立某种方式将您的二进制文件传播到部署管道的后期阶段。流行的开发环境附带的一些简单的配置管理工具会做错事。一个值得注意的例子是项目模板,它直接生成包含代码和配置文件(例如 ear 和 war 文件)的程序集,作为构建过程中的一个步骤。

If you take our advice, it will initially feel as though you have more work to do. You will need to establish some way of propagating your binaries to the later stages in the deployment pipeline, if your CI tool doesn’t do this for you already. Some of the simplistic configuration management tools that come with popular development environments will be doing the wrong thing. A notable example of this is project templates that directly generate assemblies containing both code and configuration files, such as ear and war files, as a single step in the build process.


这一原则的一个重要推论是,必须可以将这些二进制文件部署到每个环境。这迫使您将代码(在环境之间保持相同)和配置(在环境之间不同)分开。反过来,这将引导您正确地管理您的配置,对结构更好的构建系统施加温和的压力。

One important corollary of this principle is that it must be possible to deploy these binaries to every environment. This forces you to separate code, which remains the same between environments, and configuration, which differs between environments. This, in turn, will lead you to managing your configuration correctly, applying a gentle pressure towards better-structured build systems.

这将我们巧妙地带到下一个练习中。

This brings us neatly to the next practice.

以相同的方式部署到每个环境

Deploy the Same Way to Every Environment

必须使用相同的流程部署到每个环境——无论是开发人员或分析师的工作站、测试环境还是生产环境——以确保构建和部署流程得到有效测试。开发人员一直在部署;测试人员和分析师,较少;通常,您很少会部署到生产环境。但是这种部署频率与每个环境相关的风险是相反的。您部署到最少的环境(生产)是最重要的。只有在许多环境中对部署过程进行了数百次测试之后,您才能将部署脚本作为错误源消除。

It is essential to use the same process to deploy to every environment—whether a developer or analyst’s workstation, a testing environment, or production—in order to ensure that the build and deployment process is tested effectively. Developers deploy all the time; testers and analysts, less often; and usually, you will deploy to production fairly infrequently. But this frequency of deployment is the inverse of the risk associated with each environment. The environment you deploy to least frequently (production) is the most important. Only after you have tested the deployment process hundreds of times on many environments can you eliminate the deployment script as a source of error.

每个环境在某种程度上都是不同的。如果不出意外,它会有一个唯一的 IP 地址,但通常还有其他差异:操作系统和中间件配置设置、数据库和外部服务的位置,以及其他需要在部署时设置的配置信息。这并不意味着您应该为每个环境使用不同的部署脚本。相反,将每个环境的唯一设置分开。一种方法是使用属性文件来保存配置信息。您可以为每个环境创建一个单独的属性文件。这些文件应该签入版本控制,并通过查看本地服务器的主机名来选择正确的文件,或者(在多机环境中)通过使用提供给部署脚本的环境变量。提供部署时配置的其他一些方法包括将其保存在目录中服务(如 LDAP 或 ActiveDirectory)或将其存储在数据库中并通过 ESCAPE [apvrEr] 等应用程序访问它。在第 39 页的“管理软件配置”部分中有更多关于管理软件配置的信息

Every environment is different in some way. If nothing else, it will have a unique IP address, but often there are other differences: operating system and middleware configuration settings, the location of databases and external services, and other configuration information that needs to be set at deployment time. This does not mean you should use a different deployment script for every environment. Instead, keep the settings that are unique for each environment separate. One way to do this is to use properties files to hold configuration information. You can have a separate properties file for each environment. These files should be checked in to version control, and the correct one selected either by looking at the hostname of the local server, or (in a multimachine environment) through the use of an environment variable supplied to the deployment script. Some other ways to supply deploy-time configuration include keeping it in a directory service (like LDAP or ActiveDirectory) or storing it in a database and accessing it through an application like ESCAPE [apvrEr]. There is more on managing software configuration in the “Managing Software Configuration” section on page 39.


图片

对每个应用程序使用相同的部署时配置机制很重要。在大公司或使用多种异构技术的公司中尤其如此。一般来说,我们反对从高层下达命令——但我们已经看到太多的组织,在这些组织中,对于给定环境中的给定应用程序,在部署时实际提供了什么配置是非常困难的。我们知道您必须向不同大陆的不同团队发送电子邮件以拼凑这些信息的地方。当您试图找出某些错误的根本原因时,这会成为效率的巨大障碍——当您将它引入到您的价值流中的延迟加在一起时,它的成本是难以置信的。

It’s important to use the same deploy-time configuration mechanism for each of your applications. This is especially true in a large company, or where many heterogeneous technologies are in play. Generally, we’re against handing down edicts from on high—but we’ve seen too many organizations where it was impossibly arduous to work out, for a given application in a given environment, what configuration was actually supplied at deployment time. We know places where you have to email separate teams on separate continents to piece together this information. This becomes an enormous barrier to efficiency when you’re trying to work out the root cause of some bug—and when you add together the delays it introduces into your value stream, it is incredibly costly.

应该可以查阅一个单一来源(版本控制存储库、目录服务或数据库)来查找所有环境中所有应用程序的配置设置。

It should be possible to consult one single source (a version control repository, a directory service, or a database) to find configuration settings for all your applications in all of your environments.


如果您工作的公司生产环境由与负责开发和测试环境的团队不同的团队管理,则两个团队将需要共同努力以确保自动化部署过程在所有环境(包括开发环境)中有效运行。使用用于部署到开发环境的相同脚本部署到生产环境是防止“它在我的机器上工作”综合症 [c29ETR] 的绝佳方式。这也意味着当您开始发布时,您将通过部署到所有其他环境来测试您的部署过程数百次。这是我们所知道的降低软件发布风险的最佳方法之一。

If you work in a company where production environments are managed by a team different from the team responsible for development and testing environments, both teams will need to work together to make sure the automated deployment process works effectively across all environments, including development environments. Using the same script to deploy to production that you use to deploy to development environments is a fantastic way to prevent the “it works on my machine” syndrome [c29ETR]. It also means that when you come to release, you will have tested your deployment process hundreds of times by deploying to all of your other environments. This is one of the best ways we know to mitigate the risk of releasing software.


图片

我们假设您有一个自动部署应用程序的过程——但是,当然,许多组织仍然手动部署。如果您有一个手动部署过程,您应该首先确保该过程对于每个环境都是相同的,然后开始一点一点地使其自动化,以使其完全脚本化为目标。最终,您只需指定目标环境和应用程序版本即可启动成功部署。自动化、标准化的部署流程将对您以可重复和可靠的方式发布应用程序的能力产生巨大的积极影响,并确保该流程得到完整的记录和审计。我们将在下一章详细介绍自动化部署。

We’ve assumed that you have an automated process for deploying your application—but, of course, many organizations still deploy manually. If you have a manual deployment process, you should start by ensuring that the process is the same for every environment and then begin to automate it bit by bit, with the goal of having it fully scripted. Ultimately, you should only need to specify the target environment and the version of the application to initiate a successful deployment. An automated, standardized deployment process will have a huge positive effect on your ability to release your application repeatably and reliably, and ensure that the process is completely documented and audited. We cover automating deployment in detail in the following chapter.


这个原则实际上是你应该把变化和不变化分开的规则的另一个应用。如果您的部署脚本对于不同的环境是不同的,您将无法知道您正在测试的内容在您上线时是否真的有效。相反,如果您使用相同的过程在任何地方进行部署,当部署不适用于特定环境时,您可以将其缩小为以下三个原因之一:

This principle is really another application of the rule that you should separate what changes from what doesn’t. If your deployment script is different for different environments, you have no way of knowing that what you’re testing will actually work when you go live. Instead, if you use the same process to deploy everywhere, when a deployment doesn’t work to a particular environment you can narrow it down to one of three causes:

• 应用程序特定于环境的配置文件中的设置

• A setting in your application’s environment-specific configuration file

• 您的基础架构或您的应用程序所依赖的服务之一存在问题

• A problem with your infrastructure or one of the services on which your application depends

• 环境配置

• The configuration of your environment

确定其中哪一个是根本原因是接下来两个实践的主题。

Establishing which of these is the underlying cause is the subject of the next two practices.

对您的部署进​​行冒烟测试

Smoke-Test Your Deployments

当您部署您的应用程序时,您应该有一个自动脚本来进行冒烟测试以确保它已启动并正在运行。这可能与启动应用程序并检查以确保主屏幕显示预期内容一样简单。您的冒烟测试还应该检查您的应用程序所依赖的任何服务是否已启动并正在运行,例如数据库、消息传递总线或外部服务。

When you deploy your application, you should have an automated script that does a smoke test to make sure that it is up and running. This could be as simple as launching the application and checking to make sure that the main screen comes up with the expected content. Your smoke test should also check that any services your application depends on are up and running—such as a database, messaging bus, or external service.

冒烟测试或部署测试可能是单元测试套件启动并运行后要编写的最重要的测试——事实上,它甚至可以说更重要。它让您确信您的应用程序实际运行。如果它没有运行,您的冒烟测试应该能够为您提供一些基本的诊断,以判断您的应用程序是否因为它所依赖的某些东西不工作而关闭。

The smoke test, or deployment test, is probably the most important test to write once you have a unit test suite up and running—indeed, it’s arguably even more important. It gives you the confidence that your application actually runs. If it doesn’t run, your smoke test should be able to give you some basic diagnostics as to whether your application is down because something it depends on is not working.

部署到生产副本

Deploy into a Copy of Production

许多团队在上线时遇到的另一个主要问题是他们的生产环境与测试和开发环境有很大不同。为了充分确信上线会真正起作用,您需要在与生产环境尽可能相似的环境中进行测试和持续集成。

The other main problem many teams experience going live is that their production environment is significantly different from their testing and development environments. To get a good level of confidence that going live will actually work, you need to do your testing and continuous integration on environments that are as similar as possible to your production environment.

理想情况下,如果您的生产环境很简单或者您有足够大的预算,您可以拥有生产的精确副本来运行手动和自动测试。确保您的环境相同需要一定的纪律以应用良好的配置管理实践。您需要确保:

Ideally, if your production environment is simple or you have a sufficiently large budget, you can have exact copies of production to run your manual and automated tests on. Making sure that your environments are the same requires a certain amount of discipline to apply good configuration management practices. You need to ensure that:

• 您的基础设施(例如网络拓扑和防火墙配置)是相同的。

• Your infrastructure, such as network topology and firewall configuration, is the same.

• 您的操作系统配置(包括补丁)是相同的。

• Your operating system configuration, including patches, is the same.

• 您的应用程序堆栈是相同的。

• Your application stack is the same.

• 您的应用程序数据处于已知的有效状态。在执行升级时迁移数据可能是部署中的主要痛苦来源。我们将在第 12 章“管理数据”中更多地讨论这个主题。

• Your application’s data is in a known, valid state. Migrating data when performing upgrades can be a major source of pain in deployments. We deal more with this topic in Chapter 12, “Managing Data.”

您可以使用磁盘映像和虚拟化等实践以及 Puppet 和 InstallShield 等工具以及版本控制存储库来管理环境的配置。我们将在第 11 章“管理基础设施和环境”中对此进行详细讨论。

You can use such practices as disk imaging and virtualization, and tools like Puppet and InstallShield along with a version control repository, to manage your environments’ configuration. We discuss this in detail in Chapter 11, “Managing Infrastructure and Environments.”

每个更改都应立即通过管道传播

Each Change Should Propagate through the Pipeline Instantly

图 5.6 管道中的调度阶段

Figure 5.6 Scheduling stages in a pipeline

图片

在引入持续集成之前,许多项目会按计划运行其流程的各个部分——例如,构建可能每小时运行一次,验收测试每晚运行一次,而容量测试则在周末进行一次。部署管道采用不同的方法:第一阶段应在每次签入时触发,每个阶段应在成功完成后立即触发下一个阶段。当然,当开发人员(尤其是大型团队)非常频繁地签入时,这并不总是可能的,因为您的流程中的各个阶段可能会花费相当多的时间。问题如图 5.6所示。

Before continuous integration was introduced, many projects ran various parts of their process off a schedule—for example, builds might run hourly, acceptance tests nightly, and capacity tests over the weekend. The deployment pipeline takes a different approach: The first stage should be triggered upon every check-in, and each stage should trigger the next one immediately upon successful completion. Of course this is not always possible when developers (especially on large teams) are checking in very frequently, given that the stages in your process can take a not insignificant amount of time. The problem is shown in Figure 5.6.

在此示例中,有人将更改检查到版本控制中,创建版本 1。这反过来会触发管道中的第一阶段(构建和单元测试)。这通过并触发第二阶段:自动验收测试。然后有人签入另一个更改,创建版本 2。这会再次触发构建和单元测试。然而,即使这些已经通过,它们也无法触发自动验收测试的新实例,因为它们已经在运行。与此同时,又连续发生了两次签到。然而,CI 系统不应该尝试同时构建它们——如果它遵循该规则,并且开发人员继续以相同的速度签入,构建将越来越落后于开发人员当前正在做的事情。

In this example, somebody checks a change into version control, creating version 1. This, in turn, triggers the first stage in the pipeline (build and unit tests). This passes, and triggers the second stage: the automated acceptance tests. Somebody then checks in another change, creating version 2. This triggers the build and unit tests again. However, even though these have passed, they cannot trigger a new instance of the automated acceptance tests, since they are already running. In the meantime, two more check-ins have occurred in quick succession. However, the CI system should not attempt to build both of them—if it followed that rule, and developers continued to check in at the same rate, the builds would get further and further behind what the developers are currently doing.

相反,一旦构建和单元测试的实例完成,CI 系统就会检查是否有新的更改可用,如果有,则构建最新的可用集——在本例中为版本 4。假设这会破坏构建和单元测试阶段。构建系统不知道是第 3 次还是第 4 次提交导致阶段中断,但开发人员自己解决这个问题通常很简单。一些 CI 系统会让你乱序运行指定的版本,在这种情况下,开发人员可以触发修订版 3 的第一阶段,看看它是通过还是失败,从而判断是提交 3 还是提交 4 破坏了构建。无论哪种方式,开发团队都会签入版本 5,从而解决问题。

Instead, once an instance of the build and unit tests has finished, the CI system checks to see if new changes are available, and if so, builds off the most recent set available—in this case, version 4. Suppose this breaks the build and unit tests stage. The build system doesn’t know which commit, 3 or 4, caused the stage to break, but it is usually simple for the developers to work this out for themselves. Some CI systems will let you run specified versions out of order, in which case the developers could trigger the first stage off revision 3 to see if it passes or fails, and thus whether it was commit 3 or 4 that broke the build. Either way, the development team checks in version 5, which fixes the problem.

当验收测试最终完成时,CI 系统的调度程序会注意到有新的更改可用,并触发针对版本 5 重新运行验收测试。

When the acceptance tests finally finish, the CI system’s scheduler notices that new changes are available, and triggers a new run of the acceptance tests against version 5.

这种智能调度对于实施部署管道至关重要。确保你的 CI 服务器支持这种调度工作流——很多都支持——并确保更改立即传播,这样你就不必按照固定的时间表运行阶段。

This intelligent scheduling is crucial to implementing a deployment pipeline. Make sure your CI server supports this kind of scheduling workflow—many do—and ensure that changes propagate immediately so that you don’t have to run stages off a fixed schedule.

这仅适用于完全自动化的阶段,例如包含自动化测试的阶段。管道中执行部署到手动测试环境的后期阶段需要按需激活,我们将在本章后面的部分中对此进行描述。

This only applies to stages that are fully automated, such as those containing automated tests. The later stages in the pipeline that perform deployments to manual testing environments need to be activated on demand, which we describe in a later section in this chapter.

如果管道的任何部分出现故障,请停止该管道

If Any Part of the Pipeline Fails, Stop the Line

正如我们在第 56 页的“实施持续集成”部分所说,实现本书目标(快速、可重复、可靠的发布)的最重要步骤是让您的团队在每次将代码签入版本控制时接受这一点,它将成功构建并通过所有测试。这适用于整个部署管道。如果对环境的部署失败,则整个团队都将承担该失败。在做任何其他事情之前,他们应该停下来修复它。

As we said in the “Implementing Continuous Integration” section on page 56, the most important step in achieving the goals of this book—rapid, repeatable, reliable releases—is for your team to accept that every time they check code into version control, it will successfully build and pass every test. This applies to the entire deployment pipeline. If a deployment to an environment fails, the whole team owns that failure. They should stop and fix it before doing anything else.

提交阶段

The Commit Stage

每次签入时都会创建部署管道的新实例,如果第一阶段通过,则会创建候选发布版本。管道第一阶段的目的是消除不适合生产的构建,并尽快向团队发出应用程序已损坏的信号。我们希望在明显损坏的应用程序版本上花费最少的时间和精力。因此,当开发人员向版本控制系统提交更改时,我们希望快速评估应用程序的最新版本。签入的开发人员在继续下一个任务之前等待结果。

A new instance of your deployment pipeline is created upon every check-in and, if the first stage passes, results in the creation of a release candidate. The aim of the first stage in the pipeline is to eliminate builds that are unfit for production and signal the team that the application is broken as quickly as possible. We want to expend a minimum of time and effort on a version of the application that is obviously broken. So, when a developer commits a change to the version control system, we want to evaluate the latest version of the application quickly. The developer who checked in then waits for the results before moving on to the next task.

作为提交阶段的一部分,我们需要做一些事情。通常,这些任务作为一组作业在构建网格(大多数 CI 服务器提供的工具)上运行,因此该阶段在合理的时间内完成。提交阶段的运行时间最好不超过五分钟,当然不超过十分钟。提交阶段通常包括以下步骤:

There are a few things we want to do as part of our commit stage. Typically, these tasks are run as a set of jobs on a build grid (a facility provided by most CI servers) so the stage completes in a reasonable length of time. The commit stage should ideally take less than five minutes to run, and certainly no more than ten minutes. The commit stage typically includes the following steps:

• 编译代码(如有必要)。

• Compile the code (if necessary).

• 运行一组提交测试。

• Run a set of commit tests.

• 创建供后续阶段使用的二进制文件。

• Create binaries for use by later stages.

• 执行代码分析以检查其健康状况。

• Perform analysis of the code to check its health.

• 准备工件,例如测试数据库,供后续阶段使用。

• Prepare artifacts, such as test databases, for use by later stages.

第一步是编译最新版本的源代码,如果编译出错,通知自上次成功签入以来提交更改的开发人员。如果此步骤失败,我们可以立即使提交阶段失败,并从进一步考虑中消除此管道实例。

The first step is to compile the latest version of the source code and notify the developers who committed changes since the last successful check-in if there is an error in compilation. If this step fails, we can fail the commit stage immediately and eliminate this instance of the pipeline from further consideration.

接下来,运行一组测试,优化以非常快速地执行。我们将这套测试称为提交阶段测试而不是单元测试,因为尽管其中绝大多数确实是单元测试,但在此阶段包含一小部分其他类型的测试以获得更高的性能是很有用的如果提交阶段通过,则应用程序真正工作的置信度。这些是开发人员在签入代码之前运行的相同测试(或者,如果他们有能力这样做,通过构建网格上的预测试提交)。

Next, a suite of tests is run, optimized to execute very quickly. We refer to this suite of tests as commit stage tests rather than unit tests because, although the vast majority of them are indeed unit tests, it is useful to include a small selection of tests of other types at this stage in order to get a higher level of confidence that the application is really working if the commit stage passes. These are the same tests that developers run before they check in their code (or, if they have the facility to do so, through a pretested commit on the build grid).

通过运行所有单元测试来开始您的提交测试套件的设计。稍后,当您更多地了解在验收测试运行和管道的其他后期阶段常见的故障类型时,您应该将特定测试添加到您的提交测试套件中,以便尽早发现它们。这是一个持续的流程优化如果您要避免在后期管道阶段查找和修复错误的更高成本,这一点很重要。

Begin the design of your commit test suite by running all unit tests. Later, as you learn more about what types of failure are common in acceptance test runs and other later stages in the pipeline, you should add specific tests to your commit test suite to try and find them early on. This is an ongoing process optimization that is important if you are to avoid the higher costs of finding and fixing bugs in later pipeline stages.

确定您的代码可以编译并通过测试固然很好,但它并不能告诉您很多有关应用程序非功能性特征的信息。测试容量等非功能性特征可能很困难,但您可以运行分析工具,为您提供有关代码库特征的反馈,例如测试覆盖率、可维护性和安全漏洞。如果您的代码未能满足这些指标的预设阈值,那么提交阶段应该以与失败测试相同的方式失败。有用的指标包括:

Establishing that your code compiles and passes tests is great, but it doesn’t tell you a lot about the nonfunctional characteristics of your application. Testing nonfunctional characteristics such as capacity can be hard, but you can run analysis tools giving you feedback on such characteristics of your code base as test coverage, maintainability, and security breaches. Failure of your code to meet preset thresholds for these metrics should fail the commit stage the same way that a failing test does. Useful metrics include:

• 测试覆盖率(如果你的提交测试只覆盖了代码库的 5%,那它们就毫无用处)

• Test coverage (if your commit tests only cover 5% of your codebase, they’re pretty useless)

• 重复代码的数量

• Amount of duplicated code

• 圈复杂度

• Cyclomatic complexity

• 传入和传出耦合

• Afferent and efferent coupling

• 警告次数

• Number of warnings

• 代码风格

• Code style

提交阶段的最后一步,在成功执行到目前为止的一切之后,是创建一个可部署的代码程序集,以准备部署到任何后续环境中。这也必须成功才能将提交阶段视为整体成功。将可执行代码的创建本身视为成功标准是确保我们的构建过程本身也受到我们的持续集成系统不断评估和审查的一种简单方法。

The final step in the commit stage, following successful execution of everything up to this point, is the creation of a deployable assembly of your code ready for deployment into any subsequent environment. This, too, must succeed for the commit stage to be considered a success as a whole. Treating the creation of the executable code as a success criteria in its own right is a simple way of ensuring that our build process itself is also under constant evaluation and review by our continuous integration system.

提交阶段最佳实践

Commit Stage Best Practices

第 3 章中描述的大多数实践,“持续集成”,适用于commit阶段。开发人员应该等到部署管道的提交阶段成功。如果失败,他们应该要么快速解决问题,要么从版本控制中撤回他们的更改。在理想的世界——一个拥有无限处理器能力和无限网络带宽的世界——我们希望我们的开发人员等待所有测试通过,即使是手动测试,这样他们就可以立即解决任何问题。实际上,这是不切实际的,因为部署管道的后期阶段(自动验收测试、容量测试和手动验收测试)是漫长的活动。这就是流水线化测试过程的原因——尽快获得反馈很重要,当问题修复成本很低时,

Most of the practices described in Chapter 3, “Continuous Integration,” apply to the commit stage. Developers are expected to wait until the commit stage of the deployment pipeline succeeds. If it fails, they should either quickly fix the problem, or back their changes out from version control. In the ideal world—a world of infinite processor power and unlimited network bandwidth—we would like our developers to wait for all tests to pass, even the manual ones, so that they could fix any problem immediately. In reality, this is not practical, as the later stages in the deployment pipeline (automated acceptance testing, capacity testing, and manual acceptance testing) are lengthy activities. This is the reason for pipelining your test process—it’s important to get feedback as quickly as possible, when problems are cheap to fix, but not at the expense of getting more comprehensive feedback when it becomes available.

通过提交阶段是发布候选者旅程中的一个重要里程碑。它是我们流程中的一扇门,一旦通过,开发人员就可以自由地继续他们的下一个任务。然而,他们也有责任监督后期阶段的进展。修复损坏的构建仍然是开发团队的首要任务,即使这些损坏发生在管道的后期阶段。我们在赌成功——但如果赌输了,我们准备好偿还我们的技术债务。

Passing the commit stage is an important milestone in the journey of a release candidate. It is a gate in our process that, once passed, frees developers to move on to their next task. However, they retain a responsibility to monitor the progress of the later stages too. Fixing broken builds remains the top priority for the development team even when those breakages occur in the later stages of the pipeline. We are gambling on success—but are ready to pay our technical debts should our gamble fail.

如果您只在开发过程中实施提交阶段,它通常代表您的团队在可靠性和输出质量方面向前迈出了一大步。但是,完成我们认为的最小部署管道还需要几个阶段。

If you only implement a commit stage in your development process, it usually represents an enormous step forward in the reliability and quality of the output of your teams. However, there are several more stages necessary to complete what we consider to be a minimal deployment pipeline.

自动验收测试门

The Automated Acceptance Test Gate

一个全面的提交测试套件是对许多类错误的极好的试金石,但有很多错误是它无法捕捉到的。包含绝大多数提交测试的单元测试与低级 API 如此耦合,以至于开发人员通常很难避免陷入证明解决方案以特定方式工作的陷阱,而不是断言它是解决了一个特定的问题。

A comprehensive commit test suite is an excellent litmus test for many classes of errors, but there is much that it won’t catch. Unit tests, which comprise the vast majority of the commit tests, are so coupled to the low-level API that it is often hard for the developers to avoid the trap of proving that the solution works in a particular way, rather than asserting that is solves a particular problem.

针对每次签入运行的提交测试为我们提供了有关最新版本问题和我们应用程序中的小错误的及时反馈。但如果不在类似生产的环境中运行验收测试,我们就不知道应用程序是否满足客户的规范,也不知道它是否可以在现实世界中部署和生存。如果我们想要及时反馈这些主题,我们必须扩展我们持续集成过程的范围,以测试和演练我们系统的这些方面。

Commit tests that run against every check-in provide us with timely feedback on problems with the latest build and on bugs in our application in the small. But without running acceptance tests in a production-like environment, we know nothing about whether the application meets the customer’s specifications, nor whether it can be deployed and survive in the real world. If we want timely feedback on these topics, we must extend the range of our continuous integration process to test and rehearse these aspects of our system too.

我们部署管道的自动化验收测试阶段与功能验收测试的关系类似于提交阶段与单元测试的关系。大多数在验收测试阶段运行的测试是功能验收测试,但不是全部。

The relationship of the automated acceptance test stage of our deployment pipeline to functional acceptance testing is similar to that of the commit stage to unit testing. The majority of tests running during the acceptance test stage are functional acceptance tests, but not all.

验收测试阶段的目标是断言系统提供了客户期望的价值并且满足验收标准。验收测试阶段还用作回归测试套件,验证新更改没有将错误引入现有行为。正如我们在第 8 章“自动化验收测试”中所描述的,创建和维护自动化验收测试的过程不是由单独的团队执行的,而是被带入开发过程的核心并由跨职能交付团队执行的。开发人员、测试人员和客户共同创建这些测试以及单元测试和他们在正常开发过程中编写的代码。

The goal of the acceptance test stage is to assert that the system delivers the value the customer is expecting and that it meets the acceptance criteria. The acceptance test stage also serves as a regression test suite, verifying that no bugs are introduced into existing behavior by new changes. As we describe in Chapter 8, “Automated Acceptance Testing,” the process of creating and maintaining automated acceptance tests is not carried out by separate teams but is brought into the heart of the development process and carried out by cross-functional delivery teams. Developers, testers, and customers work together to create these tests alongside the unit tests and the code they write as part of their normal development process.

至关重要的是,开发团队必须立即响应作为正常开发过程的一部分发生的验收测试中断。他们必须确定损坏是引入的回归、应用程序行为的有意更改还是测试问题的结果。然后他们必须采取适当的措施使自动验收测试套件再次通过。

Crucially, the development team must respond immediately to acceptance test breakages that occur as part of the normal development process. They must decide if the breakage is a result of a regression that has been introduced, an intentional change in the behavior of the application, or a problem with the test. Then they must take the appropriate action to get the automated acceptance test suite passing again.

自动验收测试门是发布候选者生命周期中的第二个重要里程碑。部署管道将只允许后期阶段(例如手动请求的部署)访问已成功克服自动验收测试障碍的构建。虽然可以尝试并破坏系统,但这非常耗时且昂贵,因此最好将精力花在修复部署管道已识别的问题上,并以其支持的受控和可重复方式进行部署。部署管道使做正确的事比做错事更容易,因此团队做正确的事。

The automated acceptance test gate is the second significant milestone in the lifecycle of a release candidate. The deployment pipeline will only allow the later stages, such as manually requested deployments, to access builds that have successfully overcome the hurdle of automated acceptance testing. While it is possible to try and subvert the system, this is so time-consuming and expensive that the effort is much better spent on fixing the problem that the deployment pipeline has identified and deploying in the controlled and repeatable manner it supports. The deployment pipeline makes it easier to do the right thing than to do the wrong thing, so teams do the right thing.

因此,不满足其所有验收标准的候选发布版将永远不会发布给用户。

Thus a release candidate that does not meet all of its acceptance criteria will never get released to users.

自动验收测试最佳实践

Automated Acceptance Test Best Practices

考虑您的应用程序将在生产中遇到的环境很重要。如果您只部署到您控制下的单一生产环境,那您就很幸运了。只需在此副本上运行您的验收测试环境。如果生产环境复杂或昂贵,您可以使用它的缩小版本,也许使用几个中间件服务器,而在生产环境中可能有很多。如果您的应用程序依赖于外部服务,您可以对您依赖的任何外部基础设施使用测试替身。我们将在第 8 章“自动验收测试”中详细介绍这些方法。

It is important to consider the environments that your application will encounter in production. If you’re only deploying to a single production environment under your control, you’re lucky. Simply run your acceptance tests on a copy of this environment. If the production environment is complex or expensive, you can use a scaled-down version of it, perhaps using a couple of middleware servers while there might be many of them in production. If your application depends on external services, you can use test doubles for any external infrastructure that you depend on. We go into more detail on these approaches in Chapter 8, “Automated Acceptance Testing.”

如果您必须针对许多不同的环境,例如,如果您正在开发必须安装在用户计算机上的软件,则您将需要在选择的可能目标环境上运行验收测试。使用构建网格最容易实现这一点。设置一系列测试环境,每个目标测试环境至少一个,并在所有这些环境上并行运行验收测试。

If you have to target many different environments, for example if you’re developing software that has to be installed on a user’s computer, you will need to run acceptance tests on a selection of likely target environments. This is most easily accomplished with a build grid. Set up a selection of test environments, at least one for each target test environment, and run acceptance tests in parallel on all of them.

在许多完全进行自动化功能测试的组织中,一种常见的做法是有一个单独的团队专门负责测试套件的生产和维护。正如第 4 章中详细描述的那样,“实施测试策略”,这是一个坏主意。最有问题的结果是开发人员不觉得他们拥有验收测试。因此,他们往往不会关注部署流水线这一阶段的故障,从而导致它长期处于中断状态。在没有开发人员参与的情况下编写的验收测试也往往与 UI 紧密耦合,因此很脆弱且分解不好,因为测试人员对 UI 的底层设计没有任何洞察力,并且缺乏创建抽象层或针对特定对象运行验收测试的技能。公共 API。

In many organizations where automated functional testing is done at all, a common practice is to have a separate team dedicated to the production and maintenance of the test suite. As described at length in Chapter 4, “Implementing a Testing Strategy,” this is a bad idea. The most problematic outcome is that the developers don’t feel as if they own the acceptance tests. As a result, they tend not to pay attention to the failure of this stage of the deployment pipeline, which leads to it being broken for long periods of time. Acceptance tests written without developer involvement also tend to be tightly coupled to the UI and thus brittle and badly factored, because the testers don’t have any insight into the UI’s underlying design and lack the skills to create abstraction layers or run acceptance tests against a public API.

事实上,整个团队都负责验收测试,就像整个团队负责管道的每个阶段一样。如果验收测试失败,整个团队应该立即停止并修复它们。

The reality is that the whole team owns the acceptance tests, in the same way as the whole team owns every stage of the pipeline. If the acceptance tests fail, the whole team should stop and fix them immediately.

这种做法的一个重要推论是开发人员必须能够在他们的开发环境中运行自动验收测试。对于发现验收测试失败的开发人员来说,应该很容易在他们自己的机器上轻松修复它并通过在本地运行该验收测试来验证修复。最常见的障碍是所使用的测试软件的许可证不足,以及阻止系统部署在开发环境中以对其运行验收测试的应用程序架构。如果您的自动化验收测试策略要取得长期成功,就需要消除这些障碍。

One important corollary of this practice is that developers must be able to run automated acceptance tests on their development environments. It should be easy for a developer who finds an acceptance test failure to fix it easily on their own machine and verify the fix by running that acceptance test locally. The most common obstacles to this are insufficient licenses for the testing software being used and an application architecture that prevents the system from being deployed on a development environment so that the acceptance tests can be run against it. If your automated acceptance testing strategy is to succeed in the long term, these kinds of obstacles need to be removed.

验收测试很容易与应用程序中的特定解决方案紧密耦合,而不是断言系统的业务价值。当这种情况发生时,越来越多的时间花在维护验收测试上,因为系统行为的微小变化会使测试无效。验收测试应该用业务语言表达(Eric Evans 称之为“通用语言”3)、不是应用程序的技术语言。我们的意思是,虽然将验收测试写在与您的团队用于开发的相同编程语言,抽象应该在业务行为级别工作——“下订单”而不是“点击订单按钮”,“确认资金转移”而不是“检查资金表有结果”,等等.

It can be easy for acceptance tests to become too tightly coupled to a particular solution in the application rather than asserting the business value of the system. When this happens, more and more time is spent maintaining the acceptance tests as small changes in the behavior of the system invalidate tests. Acceptance tests should be expressed in the language of the business (what Eric Evans calls the “ubiquitous language”3), not in the language of the technology of the application. By this we mean that while it is fine to write the acceptance tests in the same programming language that your team uses for development, the abstraction should work at the level of business behavior—“place order” rather than “click order button,” “confirm fund transfer” rather than “check fund_table has results,” and so on.

虽然验收测试非常有价值,但创建和维护它们的成本也很高。因此,必须牢记自动验收测试也是回归测试。不要遵循采用您的验收标准并盲目地使每个标准自动化的天真过程。

While acceptance tests are extremely valuable, they can also be expensive to create and maintain. It is thus essential to bear in mind that automated acceptance tests are also regression tests. Don’t follow a naive process of taking your acceptance criteria and blindly automating every one.

我们从事的几个项目发现,由于遵循上述一些不良做法,自动化功能测试没有提供足够的价值。它们的维护成本太高,因此停止了自动化功能测试。如果测试花费的精力多于节省的精力,那么这是正确的决定,但是改变测试创建和维护的管理方式可以显着减少花费的精力并显着改变成本效益方程式。正确进行验收测试是第 8 章“自动化验收测试”的主题。

We have worked on several projects that found, as a result of following some of the bad practices described above, that the automated functional tests were not delivering enough value. They were costing far too much to maintain, and so automated functional testing was stopped. This is the right decision if the tests cost more effort than they save, but changing the way the creation and maintenance of the tests are managed can dramatically reduce the effort expended and change the cost-benefit equation significantly. Doing acceptance testing right is the main subject of Chapter 8, “Automated Acceptance Testing.”

后续测试阶段

Subsequent Test Stages

验收测试阶段是发布候选者生命周期中的一个重要里程碑。一旦这个阶段完成,一个成功的候选版本就已经从主要属于开发团队领域的东西转移到更广泛的兴趣和使用的东西。

The acceptance test stage is a significant milestone in the lifecycle of a release candidate. Once this stage has been completed, a successful release candidate has moved on from something that is largely the domain of the development team to something of wider interest and use.

对于最简单的部署流水线,通过验收测试的构建就可以发布给用户了,至少就系统的自动化测试而言是这样。如果候选人未通过此阶段,则根据定义不适合发布。

For the simplest deployment pipelines, a build that has passed acceptance testing is ready for release to users, at least as far as the automated testing of the system is concerned. If the candidate fails this stage, it by definition is not fit to be released.

发布候选人到这一点的进展是自动的,成功的候选人会自动提升到下一阶段。如果您以增量方式交付软件,则可以自动部署到生产环境,如 Timothy Fitz 的博客文章“持续部署”[dbnlG8] 中所述。但是对于许多系统来说,在发布之前进行某种形式的手动测试是可取的,即使您拥有一套全面的自动化测试也是如此。许多项目都有用于测试与其他系统集成的环境、用于测试容量的环境、探索性测试环境以及暂存和生产环境。这些环境中的每一个都或多或少类似于生产环境,并且具有自己独特的配置。

The progression of the release candidate to this point has been automatic, with successful candidates being automatically promoted to the next stage. If you are delivering software incrementally, it is possible to have an automated deployment to production, as described in Timothy Fitz’ blog entry, “Continuous Deployment” [dbnlG8]. But for many systems, some form of manual testing is desirable before release, even when you have a comprehensive set of automated tests. Many projects have environments for testing integration with other systems, environments for testing capacity, exploratory testing environments, and staging and production environments. Each of these environments can be more or less production-like and have their own unique configuration.

图 5.7 示例部署页面

Figure 5.7 Example deployment page

图片

部署管道也负责部署到测试环境。AntHill Pro 和 Go 等发布管理系统能够查看当前部署到每个环境中的内容,并在该环境中执行按钮部署。当然在幕后,这些只是运行您编写的部署脚本来执行部署。也可以基于 Hudson 或 CruiseControl 系列等开源工具构建您自己的系统来执行此操作,尽管商业工具提供开箱即用的可视化、报告和细粒度部署授权。如果您创建自己的系统,关键要求是能够看到已通过验收测试阶段的候选发布列表,有一个按钮可以将您选择的版本部署到您选择的环境中,查看哪个候选发布当前部署在每个环境中以及它来自版本控制中的哪个版本。图 5.7显示了执行这些功能的自制系统。

The deployment pipeline takes care of deployments to testing environments too. Release management systems such as AntHill Pro and Go provide the ability to see what is currently deployed into each environment and to perform a pushbutton deployment into that environment. Of course behind the scenes, these simply run the deployment scripts you have written to perform the deployment. It is also possible to build your own system to do this, based on open source tools such as Hudson or the CruiseControl family, although commercial tools provide visualizations, reporting, and fine-grained authorization of deployments out of the box. If you create your own system, the key requirements are to be able to see a list of release candidates that have passed the acceptance test stage, have a button to deploy the version of your choice into the environment of your choice, see which release candidate is currently deployed in each environment and which version in version control it came from. Figure 5.7 shows a home-brewed system that performs these functions.

对这些环境的部署可以按顺序执行,每一个都取决于前一个的成功结果,因此您只能在部署到 UAT 和暂存后才能部署到生产环境。它们也可以并行发生,或者作为手动选择的可选阶段提供。

Deployments to these environments may be executed in sequence, each one depending on the successful outcome of the one before, so that you can only deploy to production once you have deployed to UAT and staging. They could also occur in parallel, or be offered as optional stages that are manually selected.

至关重要的是,部署管道允许测试人员按需将任何构建部署到他们的测试环境。这取代了“每晚构建”的概念。在部署管道中,测试人员不会根据任意修订(每个人回家前提交的最后一次更改)获得构建,而是可以看到哪些构建通过了自动化测试,对应用程序进行了哪些更改,然后选择构建他们要。如果构建结果在某些方面不令人满意——也许它没有包含正确的更改,或者包​​含一些使其不适合测试的错误——测试人员可以重新部署任何其他构建。

Crucially, the deployment pipeline allows testers to deploy any build to their testing environments on demand. This replaces the concept of the “nightly build.” In the deployment pipeline, instead of testers being given a build based on an arbitrary revision (the last change committed before everybody went home), testers can see which builds passed the automated tests, which changes were made to the application, and choose the build they want. If the build turns out to be unsatisfactory in some way—perhaps it does not include the correct change, or contains some bug which makes it unsuitable for testing—the testers can redeploy any other build.

手动测试

Manual Testing

在迭代过程中,验收测试之后总是以探索性测试、可用性测试和展示的形式进行一些手动测试。在此之前,开发人员可能已经向分析人员和测试人员展示了应用程序的功能,但这些角色都不会在尚未通过自动验收测试的构建上浪费时间。测试人员在此过程中的角色不应该是对系统进行回归测试,而是首先确保验收测试通过手动证明验收标准得到满足来真正验证系统的行为。

In iterative processes, acceptance testing is always followed by some manual testing in the form of exploratory testing, usability testing, and showcases. Before this point, developers may have demonstrated features of the application to analysts and testers, but neither of these roles will have wasted time on a build that is not known to have passed automated acceptance testing. A tester’s role in this process should not be to regression-test the system, but first of all to ensure that the acceptance tests genuinely validate the behavior of the system by manually proving that the acceptance criteria are met.

之后,测试人员专注于人类擅长而自动化测试不擅长的测试。他们进行探索性测试,对应用程序的可用性进行用户测试,检查各种平台的外观,并进行病态的最坏情况测试。自动化验收测试为测试人员腾出了时间,使他们可以专注于这些高价值的活动,而不是成为人类测试脚本执行机器。

After that, testers focus on the sort of testing that human beings excel at but automated tests are poor at. They do exploratory testing, perform user testing of the application’s usability, check the look and feel on various platforms, and carry out pathological worst-case tests. Automated acceptance testing is what frees up time for testers so they can concentrate on these high-value activities, instead of being human test-script execution machines.

非功能测试

Nonfunctional Testing

每个系统都有许多非功能性需求。例如,几乎每个系统都对容量和安全性有某种要求,或者它必须遵守的服务级别协议。运行自动化测试来衡量系统对这些要求的遵守程度通常是有意义的。有关如何实现此目的的更多详细信息,请参阅第 9 章“测试非功能性需求”。对于其他系统,非功能性需求的测试不需要持续进行。在需要的地方,根据我们的经验,在您的管道中创建一个阶段来运行这些自动化测试仍然很有价值。

Every system has many nonfunctional requirements. For example, almost every system has some kind of requirements on capacity and security, or the servicelevel agreements it must conform to. It usually makes sense to run automated tests to measure how well the system adheres to these requirements. For more details on how to achieve this, see Chapter 9, “Testing Nonfunctional Requirements.” For other systems, testing of nonfunctional requirements need not be done on a continuous basis. Where it is required, in our experience it is still valuable to create a stage in your pipeline for running these automated tests.

容量测试阶段的结果是形成一个门还是简单地告知人类决策是决定部署管道组织的标准之一。对于非常高性能的应用程序,将容量测试作为成功通过验收测试阶段的候选发布版本的完全自动化结果来运行是有意义的。如果候选人未通过容量测试,则通常不认为它是可部署的。

Whether the results of the capacity test stage form a gate or simply inform human decision-making is one of the criteria that determine the organization of the deployment pipeline. For very high-performance applications, it makes sense to run capacity testing as a wholly automated outcome of a release candidate successfully passing the acceptance test stage. If the candidate fails capacity testing, it is not usually deemed to be deployable.

但是,对于许多应用程序而言,判断什么是可接受的比这更主观。在容量测试阶段结束时呈现结果并允许人来决定是否应该提升候选版本更有意义。

For many applications, though, the judgment of what is deemed acceptable is more subjective than that. It makes more sense to present the results at the conclusion of the capacity test stage and allow a human being to decide whether the release candidate should be promoted or not.

准备发布

Preparing to Release

生产系统的每次发布都存在业务风险。充其量,如果在发布时出现严重问题,可能会延迟引入有价值的新功能。在最坏的情况下,如果没有明智的退出计划到位,它可能会使企业没有关键任务资源,因为它们必须作为新系统发布的一部分退役。

There is a business risk associated with every release of a production system. At best, if there is a serious problem at the point of release, it may delay the introduction of valuable new capabilities. At worst, if there is no sensible back-out plan in place, it may leave the business without mission-critical resources because they had to be decommissioned as part of the release of the new system.

当我们将发布步骤视为部署管道的自然结果时,缓解这些问题非常简单。从根本上说,我们想要

The mitigation of these problems is very simple when we view the release step as a natural outcome of our deployment pipeline. Fundamentally, we want to

• 制定由参与交付软件的每个人创建和维护的发布计划,包括开发人员和测试人员,以及运营人员、基础设施人员和支持人员

• Have a release plan that is created and maintained by everybody involved in delivering the software, including developers and testers, as well as operations, infrastructure, and support personnel

• 从最容易出错的阶段开始,通过尽可能多地自动化流程,最大限度地减少人员犯错的影响

• Minimize the effect of people making mistakes by automating as much of the process as possible, starting with the most error-prone stages

• 经常在类似于生产的环境中排练该过程,以便您可以调试该过程和支持它的技术

• Rehearse the procedure often in production-like environments, so you can debug the process and the technology supporting it

• 如果事情没有按计划进行,有能力取消发布

• Have the ability to back out a release if things don’t go according to plan

• 制定迁移配置和生产数据的策略,作为升级和回滚流程的一部分

• Have a strategy for migrating configuration and production data as part of the upgrade and rollback processes

我们的目标是完全自动化的发布过程。发布应该像选择要发布的应用程序版本并按下按钮一样简单。退出应该一样简单。第 10 章“部署和发布应用程序”中有关于这些主题的更多信息。

Our goal is a completely automated release process. Releasing should be as simple as choosing a version of the application to release and pressing a button. Backing out should be just as simple. There is a great deal more information on these topics in Chapter 10, “Deploying and Releasing Applications.”

自动化部署和发布

Automating Deployment and Release

我们对代码执行环境的控制越少,出现意外行为的可能性就越大。因此,每当我们发布软件系统时,我们都希望能够控制部署的每一位。有两个因素可能不利于这一理想。首先是对于许多应用程序,您根本无法完全控制您创建的软件的操作环境。对于用户安装的产品和应用程序尤其如此,例如游戏或办公应用程序。通过选择目标环境的代表性样本并在每个样本环境中并行运行自动验收测试套件,通常可以缓解此问题。然后,您可以挖掘生成的数据以确定哪些测试在哪些平台上失败。

The less control we have over the environment in which our code executes, the more potential there is for unexpected behaviors. Thus, whenever we release a software system, we want to be in control of every single bit that is deployed. There are two factors that may work against this ideal. The first is that for many applications, you simply don’t have full control of the operational environment of the software that you create. This is especially true of products and applications that are installed by users, such as games or office applications. This problem is generally mitigated by selecting a representative sample of target environments and running your automated acceptance test suite on each of these sample environments in parallel. You can then mine the data produced to work out which tests fail on which platforms.

第二个限制是建立这种控制程度的成本通常被认为超过收益。然而,通常情况恰恰相反:生产环境的大多数问题都是由于控制不力造成的。正如我们在第 11 章中所描述的,生产环境应该被完全锁定——对它们的更改只能通过自动化流程进行。这不仅包括应用程序的部署,还包括对其配置、软件堆栈、网络拓扑和状态的更改。只有这样,才能可靠地审计它们、诊断问题并修复它们一个可预测的时间。随着系统复杂性的增加,不同类型服务器的数量也会增加,并且所需的性能水平越高,这种控制水平就变得越重要。

The second constraint is that the cost of establishing that degree of control is usually assumed to outweigh the benefits. However, usually the converse is true: Most problems with production environments are caused by insufficient control. As we describe in Chapter 11, production environments should be completely locked down—changes to them should only be made through automated processes. That includes not only deployment of your application, but also changes to their configuration, software stack, network topology, and state. Only in this way is it possible to reliably audit them, diagnose problems, and repair them in a predictable time. As the complexity of the system increases, so does the number of different types of servers, and the higher the level of performance required, the more vital this level of control becomes.

管理生产环境的过程应该用于其他测试环境,例如暂存、集成等。通过这种方式,您可以使用自动变更管理系统在手动测试环境中创建完美调整的配置。这些可以调整到完美,也许使用容量测试的反馈来评估您所做的配置更改。当您对结果感到满意时,您可以以可预测、可靠的方式将其复制到每台需要该配置的服务器,包括生产服务器。环境的所有方面都应该以这种方式进行管理,包括中间件(数据库、Web 服务器、消息代理和应用程序服务器)。每个都可以进行调整和微调,并将最佳设置添加到您的配置基线中。

The process for managing your production environment should be used for your other testing environments such as staging, integration, and so forth. In this way you can use your automated change management system to create a perfectly tuned configuration in your manual testing environments. These can be tuned to perfection, perhaps using feedback from capacity testing to evaluate the configuration changes that you make. When you are happy with the result, you can replicate it to every server that needs that configuration, including production, in a predictable, reliable way. All aspects of the environment should be managed in this way, including middleware (databases, web servers, message brokers, and application servers). Each can be tuned and tweaked, with the optimal settings added to your configuration baseline.

通过使用环境的自动化供应和管理、良好的配置管理实践和(如果适用)虚拟化,可以显着降低环境供应和维护自动化的成本。

The costs of automating the provision and maintenance of environments can be lowered significantly by using automated provisioning and management of environments, good configuration management practices, and (if appropriate) virtualization.

正确管理环境配置后,即可部署应用程序。其细节因系统中采用的技术而异,但步骤总是非常相似。我们在创建构建和部署脚本的方法中利用了这种相似性,这在第 6 章“构建和部署脚本”中进行了讨论,在我们监控流程的方式中也有类似之处。

Once the environment’s configuration is managed correctly, the application can be deployed. The details of this vary widely depending on the technologies employed in the system, but the steps are always very similar. We exploit this similarity in our approach to the creation of build and deployment scripts, discussed in Chapter 6, “Build and Deployment Scripting,” and in the way in which we monitor our process.

通过自动化部署和发布,交付过程变得民主化。开发人员、测试人员和运营团队不再需要依赖票务系统和电子邮件线程来部署构建,这样他们就可以收集有关系统生产准备情况的反馈。测试人员可以决定他们在测试环境中需要哪个版本的系统,而无需自己成为技术专家,也无需依赖此类专业知识的可用性来进行部署。由于部署很简单,他们可以更频繁地更改测试中的构建,也许在发现特别有趣的错误时返回系统的早期版本,将其行为与最新版本的行为进行比较。销售人员可以访问最新版本的应用程序,该应用程序具有杀手级功能,可以与客户达成交易。还有更细微的变化。根据我们的经验,人们开始放松一点。他们认为整个项目的风险较小——主要是因为它风险较小。

With automated deployment and release, the process of delivery becomes democratized. Developers, testers, and operations teams no longer need to rely on ticketing systems and email threads to get builds deployed so they can gather feedback on the production readiness of the system. Testers can decide which version of the system they want in their test environment without needing to be technical experts themselves, nor relying on the availability of such expertise to make the deployment. Since deployment is simple, they can change the build under test more often, perhaps returning to an earlier version of the system to compare its behavior with that of the latest version when they find a particularly interesting bug. Sales people can access the latest version of the application with the killer feature that will swing the deal with a client. There are more subtle changes too. In our experience, people begin to relax a little. They perceive the project as a whole as less risky—mainly because it is less risky.

降低风险的一个重要原因是发布过程本身被排练、测试和完善的程度。由于您使用相同的过程将系统部署到每个环境并发布它,因此部署过程的测试非常频繁——可能一天多次。在您顺利部署了一个复杂系统第五十次或第一百次之后,您就不会再将其视为一件大事。我们的目标是尽快到达那个阶段。如果我们想完全相信发布过程和技术,我们必须定期使用它并证明它是好的,就像我们系统的任何其他方面一样。应该可以通过部署管道以尽可能少的时间和仪式将单个更改部署到生产中。应该持续评估和改进发布过程,在尽可能接近引入问题的地方发现任何问题。

An important reason for the reduction in risk is the degree to which the process of release itself is rehearsed, tested, and perfected. Since you use the same process to deploy your system to each of your environments and to release it, the deployment process is tested very frequently—perhaps many times a day. After you have deployed a complex system for the fiftieth or hundredth time without a hitch, you don’t think about it as a big event any more. Our goal is to get to that stage as quickly as possible. If we want to be wholly confident in the release process and the technology, we must use it and prove it to be good on a regular basis, just like any other aspect of our system. It should be possible to deploy a single change to production through the deployment pipeline with the minimum possible time and ceremony. The release process should be continuously evaluated and improved, identifying any problems as close to the point at which they were introduced as possible.

许多企业需要能够每天多次发布其软件的新版本。即使是产品公司也经常需要快速向用户提供新版本的软件,以防发现严重缺陷或安全漏洞。本书中的部署管道和相关实践使安全可靠地执行此操作成为可能。尽管许多敏捷开发流程在频繁发布到生产中而蓬勃发展——我们在适用时强烈推荐这一流程——但这样做并不总是有意义。有时我们必须做很多工作才能发布一组对我们的用户作为一个整体有意义的功能,特别是在产品开发领域。然而,即使您不需要每天多次发布您的软件,

Many businesses require the ability to release new versions of their software several times a day. Even product companies often need to make new versions of their software available to users quickly, in case critical defects or security holes are found. The deployment pipeline and the associated practices in this book are what makes it possible to do this safely and reliably. Although many agile development processes thrive on frequent release into production—a process we recommend very strongly when it is applicable—it doesn’t always make sense to do so. Sometimes we have to do a lot of work before we are in a position to release a set of features that makes sense to our users as a whole, particularly in the realm of product development. However, even if you don’t need to release your software several times a day, the process of implementing a deployment pipeline will still make an enormous positive impact on your organization’s ability to deliver software rapidly and reliably.

取消更改

Backing Out Changes

传统上担心发布日的原因有两个。第一个是害怕引入问题,因为有人可能会在软件发布的手动步骤中犯下难以察觉的错误,或者因为说明中存在错误。第二个担心是,如果发布失败,要么是因为发布过程中的问题,要么是因为新版本软件的缺陷,你就要承担责任。在任何一种情况下,唯一的希望就是你足够聪明,能够很快地解决问题。

There are two reasons why release days are traditionally feared. The first one is the fear of introducing a problem because somebody might make a hard-to-detect mistake while going through the manual steps of a software release, or because there is a mistake in the instructions. The second fear is that, should the release fail, either because of a problem in the release process or a defect in the new version of the software, you are committed. In either case, the only hope is that you will be clever enough to solve the problem very quickly.

我们通过基本上每天多次排练发布来缓解第一个问题,证明我们的自动部署系统有效。通过提供退出策略可以减轻第二种恐惧。在最坏的情况下,您可以回到开始发布之前的状态,这样您就可以花时间评估问题并找到明智的解决方案。

The first problem we mitigate by essentially rehearsing the release many times a day, proving that our automated deployment system works. The second fear is mitigated by providing a back-out strategy. In the worst case, you can then get back to where you were before you began the release, which allows you to take time to evaluate the problem and find a sensible solution.

一般来说,最好的回退策略是在发布新版本时保持应用程序的先前版本可用——以及之后的一段时间。这是我们在第 10 章“部署和发布应用程序”中讨论的一些部署模式的基础。在一个非常简单的应用程序中,这可以通过将每个版本放在一个目录中并使用符号链接指向当前版本来实现(忽略数据和配置迁移)。通常,与部署和回滚相关的最复杂的问题是迁移生产数据。这在第 12 章“管理数据”中进行了详细讨论。

In general, the best back-out strategy is to keep the previous version of your application available while the new version is being released—and for some time afterwards. This is the basis for some of the deployment patterns we discuss in Chapter 10, “Deploying and Releasing Applications.” In a very simple application, this can be achieved (ignoring data and configuration migrations) by having each release in a directory and using a symlink to point to the current version. Usually, the most complex problem associated with both deploying and rolling back is migrating the production data. This is discussed at length in Chapter 12, “Managing Data.”

下一个最佳选择是从头开始重新部署应用程序的先前良好版本。为此,您应该能够单击一个按钮来发布已通过所有测试阶段的应用程序的任何版本,就像您可以在部署管道控制下的其他环境中一样。对于某些系统来说,这种理想主义的立场是完全可以实现的,即使对于与它们相关联的大量数据的系统也是如此。然而,对于某些系统,即使对于个别更改,提供完整的、版本中立的回退的成本即使不是金钱,也可能在时间上过高。然而,理想是有用的,因为它设定了每个项目都应该努力实现的目标。即使它在某些方面有些不足,您越接近这个理想位置,您的部署就越容易。

The next best option is to redeploy the previous good version of your appliation from scratch. To this end, you should have the ability to click a button to release any version of your application that has passed all stages of testing, just as you can with other environments under the control of the deployment pipeline. This idealistic position is fully achievable for some systems, even for systems with significant amounts of data associated with them. However, for some systems, even for individual changes, the cost of providing a full, version-neutral backout may be excessive in time, if not money. Nevertheless, the ideal is useful, because it sets a target which every project should strive to achieve. Even if it falls somewhat short in some respects, the closer you approach this ideal position the easier your deployment becomes.

在任何情况下,您都不应该使用与部署或执行增量部署或回滚不同的流程来退出。这些过程很少经过测试,因此不可靠。它们也不会从已知良好的基线开始,因此会很脆弱。始终通过保留旧版本的应用程序部署或完全重新部署以前已知的良好版本来回滚。

On no account should you have a different process for backing out than you do for deploying, or perform incremental deployments or rollbacks. These processes will be rarely tested and therefore unreliable. They will also not start from a known-good baseline, and therefore will be brittle. Always roll back either by keeping an old version of the application deployed or by completely redeploying a previous known-good version.

以成功为基础

Building on Success

到发布候选版本可用于部署到生产环境时,我们将确定以下关于它的断言是正确的:

By the time a release candidate is available for deployment into production, we will know with certainty that the following assertions about it are true:

• 代码可以编译。

• The code can compile.

• 代码按照我们的开发人员的想法行事,因为它通过了单元测试。

• The code does what our developers think it should because it passed its unit tests.

• 该系统按照我们的分析师或用户认为应该的方式运行,因为它通过了所有验收测试。

• The system does what our analysts or users think it should because it passed all of the acceptance tests.

• 基础设施和基线环境的配置得到适当管理,因为应用程序已经在模拟生产环境中进行了测试。

• Configuration of infrastructure and baseline environments is managed appropriately, because the application has been tested in an analog of production.

• 代码具有所有正确的组件,因为它是可部署的。

• The code has all of the right components in place because it was deployable.

• 部署系统之所以有效,是因为它至少会在开发环境中、在验收测试阶段和在测试环境中至少使用过一次,然后才能将候选版本提升到此版本阶段。

• The deployment system works because, at a minimum, it will have been used on this release candidate at least once in a development environment, once in the acceptance test stage, and once in a testing environment before the candidate could have been promoted to this stage.

• 版本控制系统拥有我们需要部署的所有内容,无需人工干预,因为我们已经多次部署了该系统。

• The version control system holds everything we need to deploy, without the need for manual intervention, because we have already deployed the system several times.

这种“以成功为基础”的方法,与我们尽快使流程或流程的任何部分失败的信条相结合,适用于各个层面。

This “building upon success” approach, allied with our mantra of failing the process or any part of it as quickly as possible, works at every level.

实施部署管道

Implementing a Deployment Pipeline

无论您是从头开始一个新项目,还是尝试为现有系统创建自动化管道,您通常都应该采用增量方法来实施部署管道。在本节中,我们将制定从无到有的策略。通常,步骤如下所示:

Whether you’re starting a new project from scratch or trying to create an automated pipeline for an existing system, you should generally take an incremental approach to implementing a deployment pipeline. In this section we’ll set out a strategy for going from nothing to a complete pipeline. In general, the steps look like this:

1. 为您的价值流建模并创建行走骨架。

1. Model your value stream and create a walking skeleton.

2. 自动化构建和部署过程。

2. Automate the build and deployment process.

3. 自动化单元测试和代码分析。

3. Automate unit tests and code analysis.

4. 自动化验收测试。

4. Automate acceptance tests.

5. 自动化发布。

5. Automate releases.

为您的价值流建模并创建行走骨架

Modeling Your Value Stream and Creating a Walking Skeleton

如本章开头所述,第一步是绘制出从签入到发布的价值流部分。如果您的项目已经启动并正在运行,您可以使用铅笔和纸在大约半小时内完成。去和参与这个过程的每个人谈谈,并写下步骤。包括对经过时间和增值时间的最佳猜测。如果您正在从事一个新项目,您将必须想出一个合适的价值流。一种方法是查看同一组织内具有与您相似特征的另一个项目。或者,您可以从最低限度开始:一个构建应用程序并运行基本指标和单元测试的提交阶段,一个运行验收测试的阶段,

As described at the beginning of this chapter, the first step is to map out the part of your value stream that goes from check-in to release. If your project is already up and running, you can do this in about half an hour using pencil and paper. Go and speak to everybody involved in this process, and write down the steps. Include best guesses for elapsed time and value-added time. If you’re working on a new project, you will have to come up with an appropriate value stream. One way to do this is to look at another project within the same organization that has characteristics similar to yours. Alternatively, you could start with a bare minimum: a commit stage to build your application and run basic metrics and unit tests, a stage to run acceptance tests, and a third stage to deploy your application to a production-like environment so you can demo it.

一旦你有了价值流图,你就可以继续在你的持续集成和发布管理工具中为你的流程建模。如果您的工具不允许您直接对价值流建模,您可以通过使用项目之间的依赖关系来模拟它。这些项目中的每一个一开始都不应该做任何事情——它们只是您可以依次触发的占位符。使用我们的“最低限度”示例,每次有人签入版本控制时都应运行提交阶段。运行验收测试的阶段应该在提交阶段通过时自动触发,使用在提交阶段创建的相同二进制文件。将二进制文件部署到类似生产环境以进行手动测试或发布的任何阶段都应该要求您按下按钮以选择要部署的版本,

Once you have a value stream map, you can go ahead and model your process in your continuous integration and release management tool. If your tool doesn’t allow you to model your value stream directly, you can simulate it by using dependencies between projects. Each of these projects should do nothing at first—they are just placeholders that you can trigger in turn. Using our “bare minimum” example, the commit stage should be run every time somebody checks in to version control. The stage that runs the acceptance tests should trigger automatically when the commit stage passes, using the same binary created in the commit stage. Any stages that deploy the binaries to a production-like environment for manual testing or release purposes should require you to press a button in order to select the version to deploy, and this capability will usually require authorization.

然后,您可以让这些占位符实际执行某些操作。如果您的项目已经在进行中,这意味着插入您现有的构建、测试和部署脚本。如果没有,你的目标是创建一个“行走的骨架”[bEUuac],这意味着做尽可能少的工作来获得所有关键元素。首先,让提交阶段正常工作。如果您还没有任何代码或单元测试,只需创建最简单的“Hello world”示例,或者对于 Web 应用程序,单个 HTML 页面,然后放置一个断言为真的单元测试。然后您就可以进行部署了——也许在 IIS 上设置一个虚拟目录并将您的网页放入其中。最后,您可以进行验收测试——您需要在完成部署后进行此操作,因为您需要部署应用程序才能对其进行验收测试。您的验收测试可以启动 WebDriver 或 Sahi 并验证网页是否包含文本“Hello world”。

You can then make these placeholders actually do something. If your project is already well under way, that means plugging in your existing build, test, and deploy scripts. If not, your aim is to create a “walking skeleton” [bEUuac], which means doing the smallest possible amount of work to get all the key elements in place. First of all, get the commit stage working. If you don’t have any code or unit tests yet, just create the simplest possible “Hello world” example or, for a web application, a single HTML page, and put a single unit test in place that asserts true. Then you can do the deployment—perhaps setting up a virtual directory on IIS and putting your web page into it. Finally, you can do the acceptance test—you need to do this after you’ve done the deployment, since you need your application deployed in order to run acceptance tests against it. Your acceptance test can crank up WebDriver or Sahi and verify that the web page contains the text “Hello world.”

在新项目中,所有这些都应该在开发工作开始之前完成——如果您使用的是迭代开发过程,则作为迭代零的一部分。您组织的系统管理员或操作人员应参与设置类似生产的环境以运行演示并开发脚本以将您的应用程序部署到其中。在以下部分中,将详细介绍如何创建行走骨架并随着项目的发展对其进行开发。

On a new project, all this should be done before work starts on development—as part of iteration zero, if you’re using an iterative development process. Your organization’s system administrators or operations personnel should be involved in setting up a production-like environment to run demos from and developing the scripts to deploy your application to it. In the following sections, there’s more detail on how to create the walking skeleton and develop it as your project grows.

自动化构建和部署过程

Automating the Build and Deployment Process

实施管道的第一步是自动化构建和部署过程。构建过程将源代码作为输入并生成二进制文件作为输出。“二进制文件”是一个故意含糊的词,因为您的构建过程产生的结果将取决于您使用的技术。二进制文件的关键特征是你应该能够将它们复制到新机器上,并且在给定一个适当配置的环境和该环境中应用程序的正确配置的情况下,启动你的应用程序——而不依赖于你的开发工具链的任何部分安装在那台机器上。

The first step in implementing a pipeline is to automate the build and deployment process. The build process takes source code as its input and produces binaries as output. “Binaries” is a deliberately vague word, since what your build process produces will depend on what technology you’re using. The key characteristic of binaries is that you should be able to copy them onto a new machine and, given an appropriately configured environment and the correct configuration for the application in that environment, start your application—without relying on any part of your development toolchain being installed on that machine.

每次有人通过您的持续集成服务器软件签入时,都应该执行构建过程。使用第 56 页“实施持续集成”部分中列出的众多工具之一您的 CI 服务器应该配置为监视您的版本控制系统,在每次对其进行更改时检查或更新您的源代码,运行自动构建过程,并将二进制文件存储在整个团队都可以访问的文件系统中通过 CI 服务器的用户界面。

The build process should be performed every time someone checks in by your continuous integration server software. Use one of the many tools listed in the “Implementing Continuous Integration” section on page 56. Your CI server should be configured to watch your version control system, check out or update your source code every time a change is made to it, run the automated build process, and store the binaries on the filesystem where they are accessible to the whole team via the CI server’s user interface.

一旦启动并运行了持续的构建过程,下一步就是自动化部署。首先,您需要一台机器来部署您的应用程序。对于新项目,这可以是您的持续集成服务器所在的机器。对于一个比较成熟的项目,你可能需要找几台机器。根据您组织的约定,此环境可称为暂存或用户验收测试 (UAT) 环境。任何一个这样,这个环境应该有点像生产环境,如第 10 章“部署和发布应用程序”中所述,其供应和维护应该是一个完全自动化的过程,如第 11 章“管理基础设施和环境”中所述。

Once you have a continuous build process up and running, the next step is automating deployment. First of all, you need to get a machine to deploy your application on. For a new project, this can be the machine your continuous integration server is on. For a project that is more mature, you may need to find several machines. Depending on your organization’s conventions, this environment can be called the staging or user acceptance testing (UAT) environment. Either way, this environment should be somewhat production-like, as described in Chapter 10, “Deploying and Releasing Applications,” and its provisioning and maintenance should be a fully automated process, as described in Chapter 11, “Managing Infrastructure and Environments.”

第 6 章“构建和部署脚本”中讨论了几种常见的部署自动化方法。部署可能涉及首先打包您的应用程序,如果应用程序的不同部分需要安装在不同的机器上,可能会打包成几个单独的包。接下来,安装和配置应用程序的过程应该是自动化的。最后,您应该编写某种形式的自动化部署测试来验证应用程序是否已成功部署。部署过程的可靠性很重要,因为它也被用作自动化验收测试的先决条件。

Several common approaches to deployment automation are discussed in Chapter 6, “Build and Deployment Scripting.” Deployment may involve packaging your application first, perhaps into several separate packages if different parts of the application need to be installed on separate machines. Next, the process of installing and configuring your application should be automated. Finally, you should write some form of automated deployment test that verifies that the application has been successfully deployed. It is important that the deployment process is reliable, as it is also used as a prerequisite for automated acceptance testing.

一旦您的应用程序部署过程自动化,下一步就是能够对您的 UAT 环境执行按钮部署。配置您的 CI 服务器,以便您可以选择应用程序的任何构建并单击一个按钮来触发一个过程,该过程获取该构建生成的二进制文件、运行部署该构建的脚本并运行部署测试。确保在开发构建和部署系统时使用我们描述的原则,例如只构建一次二进制文件并将配置与二进制文件分开,以便在每个环境中都可以使用相同的二进制文件。这将确保您的项目的配置管理处于稳固的基础上。

Once your application’s deployment process is automated, the next step is to be able to perform push-button deployments to your UAT environment. Configure your CI server so that you can choose any build of your application and click a button to trigger a process that takes the binaries produced by that build, runs the script that deploys the build, and runs the deployment test. Make sure that when developing your build and deployment system you make use of the principles we describe, such as building your binaries only once and separating configuration from binaries, so that the same binaries may be used in every environment. This will ensure that the configuration management for your project is put on a sound footing.

除了用户安装的软件,发布过程应该与您用于部署到测试环境的过程相同。唯一的技术差异应该在于环境的配置。

Except for user-installed software, the release process should be the same process you use to deploy to a testing environment. The only technical differences should be in the configuration of the environment.

自动化单元测试和代码分析

Automating the Unit Tests and Code Analysis

开发部署管道的下一步是实施完整的提交阶段。这意味着在每次签入时运行单元测试、代码分析,并最终选择验收和集成测试。运行单元测试不需要任何复杂的设置,因为根据定义,单元测试不依赖于您的应用程序运行。相反,它们可以由许多 xUnit 风格的框架之一针对您的二进制文件运行。

The next step in developing your deployment pipeline is implementing a full commit stage. This means running unit tests, code analysis, and ultimately a selection of acceptance and integration tests on every check-in. Running unit tests should not require any complex setup, because unit tests by definition don’t rely on your application running. Instead, they can be run by one of the many xUnit-style frameworks against your binaries.

由于单元测试不涉及文件系统或数据库(或者它们是组件测试),因此它们也应该可以快速运行。这就是为什么您应该在构建应用程序后直接开始运行单元测试的原因。然后,您还可以针对您的应用程序运行静态分析工具,以报告有用的诊断数据,例如编码风格、代码覆盖率、圈复杂度、耦合等。

Since unit tests do not touch the filesystem or database (or they’d be component tests), they should also be fast to run. This is why you should start running your unit tests directly after building your application. You can also then run static analysis tools against your application to report useful diagnostic data such as coding style, code coverage, cyclomatic complexity, coupling, and so forth.

随着您的应用程序变得越来越复杂,您将需要编写大量的单元测试和一组组件测试。这些都应该进入commit阶段。一旦提交阶段超过五分钟,就可以将其拆分为并行运行的套件。为了做到这一点,你需要得到几个机器(或一台有大量 RAM 和几个 CPU 的机器)并使用支持拆分工作并并行运行的 CI 服务器。

As your application gets more complex, you will need to write a large number of unit tests and a set of component tests as well. These should all go into the commit stage. Once the commit stage gets over five minutes, it makes sense to split it into suites that run in parallel. In order to do this, you’ll need to get several machines (or one machine with plenty of RAM and a few CPUs) and use a CI server that supports splitting up work and running it in parallel.

自动化验收测试

Automating Acceptance Tests

管道的验收测试阶段可以重用您用于部署到测试环境的脚本。唯一不同的是,在运行冒烟测试后,需要启动验收测试框架,并在测试运行结束时收集其生成的报告进行分析。存储应用程序创建的日志也很有意义。如果您的应用程序有 GUI,您还可以使用 Vnc2swf 等工具在验收测试运行时创建屏幕录像,以帮助您调试问题。

The acceptance test phase of your pipeline can reuse the script you use to deploy to your testing environment. The only difference is that after the smoke tests are run, the acceptance test framework needs to be started up, and the reports it generates should be collected at the end of the test run for analysis. It also makes sense to store the logs created by your application. If your application has a GUI, you can also use a tool like Vnc2swf to create a screen recording as the acceptance tests are running to help you debug problems.

验收测试分为两种类型:功能性和非功能性。在任何项目的早期就开始测试非功能性参数(如容量和扩展特性)非常重要,这样您就可以了解您的应用程序是否满足其非功能性需求。在设置和部署方面,此阶段的工作方式与功能验收测试阶段完全相同。然而,测试当然会有所不同(见第 9 章,“测试非功能性需求”,了解更多关于创建此类测试的信息)。当您开始时,完全可以将验收测试和性能测试作为单个阶段的一部分进行背靠背运行。然后您可以将它们分开,以便能够轻松地区分哪一组测试失败了。一套好的自动化验收测试将帮助您追踪间歇性的和难以重现的问题,例如竞争条件、死锁和资源争用,一旦您的应用程序发布,这些问题将更难发现和调试。

Acceptance tests fall into two types: functional and nonfunctional. It is essential to start testing nonfunctional parameters such as capacity and scaling characteristics from early on in any project, so that you have some idea of whether your application will meet its nonfunctional requirements. In terms of setup and deployment, this stage can work exactly the same way as the functional acceptance testing stage. However, the tests of course will differ (see Chapter 9, “Testing Nonfunctional Requirements,” for more on creating such tests). When you start off, it is perfectly possible to run acceptance tests and performance tests back-toback as part of a single stage. You can then separate them in order to be able to distinguish easily which set of tests failed. A good set of automated acceptance tests will help you track down intermittent and hard-to-reproduce problems such as race conditions, deadlocks, and resource contention that will be a good deal harder to discover and debug once your application is released.

您创建的作为管道验收测试和提交测试阶段的一部分的测试种类当然取决于您的测试策略(请参阅第 4 章,“实施测试策略”)。但是,您应该尝试在项目生命周期的早期自动运行所需的每种类型的测试中至少获得一两个,并将它们合并到您的部署管道中。因此,您将拥有一个框架,可以随着项目的增长轻松添加测试。

The varieties of tests you create as part of the acceptance test and commit test stages of your pipeline will of course be determined by your testing strategy (see Chapter 4, “Implementing a Testing Strategy”). However, you should try and get at least one or two of each type of test you need to run automated early on in your project’s life, and incorporate them into your deployment pipeline. Thus you will have a framework that makes it easy to add tests as your project grows.

发展你的管道

Evolving Your Pipeline

我们上面描述的步骤几乎存在于我们所见过的每个价值流中,因此也存在于管道中。它们通常是自动化的首要目标。随着您的项目变得越来越复杂,您的价值流也会发生变化。流水线还有另外两种常见的潜在扩展:组件和分支。大型应用程序最好构建为一组组装在一起的组件。在这样的项目中,每个组件都有一个微型流水线,然后是一个组装所有组件并使整个应用程序通过验收测试、非功能测试,然后部署到测试、暂存和生产环境的流水线可能是有意义的。这个话题处理详见第 13 章“管理组件和依赖项”。管理分支在第 14 章“高级版本控制”中讨论。

The steps we describe above are found in pretty much every value stream, and hence pipeline, that we have seen. They are usually the first targets for automation. As your project gets more complex, your value stream will evolve. There are two other common potential extensions to the pipeline: components and branches. Large applications are best built as a set of components which are assembled together. In such projects, it may make sense to have a minipipeline for each component, and then a pipeline that assembles all the components and puts the entire application through acceptance tests, nonfunctional tests, and then deployment to testing, staging, and production environments. This topic is dealt with at length in Chapter 13, “Managing Components and Dependencies.” Managing branches is discussed in Chapter 14, “Advanced Version Control.”

管道的实施在项目之间会有很大差异,但大多数项目的任务本身是一致的。将它们用作模式可以加快任何项目的构建和部署过程的创建。然而,归根结底,管道的目的是为构建、部署、测试和发布应用程序的过程建模。管道然后确保每个更改都可以尽可能自动化地独立通过此过程。

The implementation of the pipeline will vary enormously between projects, but the tasks themselves are consistent for most projects. Using them as a pattern can speed up the creation of the build and deployment process for any project. However, ultimately, the point of the pipeline is to model your process for building, deploying, testing, and releasing your application. The pipeline then ensures that each change can pass through this process independently in as automated a fashion as possible.

当您实施管道时,您会发现您与相关人员的对话以及您实现的效率提升将反过来对您的流程产生影响。因此,记住三件事很重要。

As you implement the pipeline, you will find that the conversations you have with the people involved and the gains in efficiency you realize will, in turn, have an effect on your process. Thus it is important to remember three things.

首先,整个流水线不需要一下子实现。它应该逐步实施。如果您的流程中有一部分当前是手动的,请在您的工作流程中为其创建一个占位符。确保您的实施记录此手动过程何时开始和何时完成。这使您可以查看每个手动过程花费了多少时间,从而估计它在多大程度上是一个瓶颈。

First of all, the whole pipeline does not need to be implemented at once. It should be implemented incrementally. If there is a part of your process that is currently manual, create a placeholder for it in your workflow. Ensure your implementation records when this manual process is started and when it completes. This allows you to see how much time is spent on each manual process, and thus estimate to what extent it is a bottleneck.

其次,您的管道是有关构建、部署、测试和发布应用程序过程效率的丰富数据源。您创建的部署管道实施应记录每次流程开始和结束的时间,以及流程每个阶段的确切更改。反过来,这些数据允许您衡量从提交更改到将其部署到生产中的周期时间,以及在流程的每个阶段花费的时间(市场上的一些商业工具会为您做这件事)。因此,可以准确地看到您的流程的瓶颈是什么,并按优先顺序解决它们。

Second, your pipeline is a rich source of data on the efficiency of your process for building, deploying, testing, and releasing applications. The deployment pipeline implementation you create should record every time a process starts and finishes, and what the exact changes were that went through each stage of your process. This data, in turn, allows you to measure the cycle time from committing a change to having it deployed into production, and the time spent on each stage in the process (some of the commercial tools on the market will do this for you). Thus it becomes possible to see exactly what your process’ bottlenecks are and attack them in order of priority.

最后,您的部署管道是一个活的系统。当您不断努力改进交付流程时,您应该继续关注您的部署管道,努力改进和重构它,就像您处理您正在使用它交付的应用程序一样。

Finally, your deployment pipeline is a living system. As you work continuously to improve your delivery process, you should continue to take care of your deployment pipeline, working to improve and refactor it the same way you work on the applications you are using it to deliver.

指标

Metrics

反馈是任何软件交付过程的核心。改进反馈的最好方法是缩短反馈周期并使结果可见。您应该持续测量并以某种难以避免的方式传播测量结果,例如在墙上非常显眼的海报上,或者在专门用于显示大胆、重要结果的计算机显示器上。这种设备被称为信息辐射器。

Feedback is at the heart of any software delivery process. The best way to improve feedback is to make the feedback cycles short and the results visible. You should measure continually and broadcast the results of the measurements in some hard-to-avoid manner, such as on a very visible poster on the wall, or on a computer display dedicated to showing bold, big results. Such devices are known as information radiators.

然而,重要的问题是:你应该测量什么?您选择衡量的内容将对您团队的行为产生巨大影响(这被称为霍桑效应)。测量代码行和开发人员会写很多短的代码行。测量修复的缺陷数量,测试人员将记录可以通过与开发人员快速讨论修复的错误。

The important question, though, is: What should you measure? What you choose to measure will have an enormous influence on the behavior of your team (this is known as the Hawthorne effect). Measure the lines of code, and developers will write many short lines of code. Measure the number of defects fixed, and testers will log bugs that could be fixed by a quick discussion with a developer.

根据精益理念,必须进行全局优化,而不是局部优化。如果您花费大量时间来消除实际上不是限制交付过程的瓶颈,那么交付过程将不会有任何不同。因此,拥有一个可用于确定整个交付过程是否存在问题的全局指标非常重要。

According to the lean philosophy, it is essential to optimize globally, not locally. If you spend a lot of time removing a bottleneck that is not actually the one constraining your delivery process, you will make no difference to the delivery process. So it is important to have a global metric that can be used to determine if the delivery process as a whole has a problem.

对于软件交付过程,最重要的全局指标是周期时间。这是从决定需要实施某个功能到将该功能发布给用户之间的时间。正如 Mary Poppendieck 所问,“您的组织需要多长时间才能部署仅涉及一行代码的更改?你这样做是在可重复的、可靠的基础上吗?”4这个指标很难衡量,因为它涵盖了软件交付过程的许多部分——从分析、开发到发布。但是,它比任何其他指标更能告诉您有关流程的信息。

For the software delivery process, the most important global metric is cycle time. This is the time between deciding that a feature needs to be implemented and having that feature released to users. As Mary Poppendieck asks, “How long would it take your organization to deploy a change that involves just one single line of code? Do you do this on a repeatable, reliable basis?”4 This metric is hard to measure because it covers many parts of the software delivery process—from analysis, through development, to release. However, it tells you more about your process than any other metric.

许多项目错误地选择其他措施作为其主要指标。关注软件质量的项目通常选择测量缺陷数量。但是,这是次要措施。如果使用此度量的团队发现了一个缺陷,但需要六个月的时间来发布针对它的修复,那么知道该缺陷的存在并不是很有用。专注于缩短周期时间会鼓励提高质量的做法,例如使用作为每次签入结果运行的综合自动化测试套件。

Many projects, incorrectly, choose other measures as their primary metrics. Projects concerned with the quality of their software often choose to measure the number of defects. However, this is a secondary measure. If a team using this measure discovers a defect, but it takes six months to release a fix for it, knowing that the defect exists is not very useful. Focusing on the reduction of cycle time encourages the practices that increase quality, such as the use of a comprehensive automated suite of tests that is run as a result of every check-in.

部署管道的正确实施应该可以简化计算与从签入到发布的价值流部分相对应的周期时间部分。它还应该让您看到从签到到流程每个阶段的准备时间,这样您就可以发现瓶颈。

A proper implementation of a deployment pipeline should make it simple to calculate the part of the cycle time corresponding to the part of the value stream from check-in to release. It should also let you see the lead time from the check-in to each stage of your process, so you can discover your bottlenecks.

一旦了解了应用程序的周期时间,就可以找出缩短周期的最佳方法。您可以使用约束理论通过应用以下过程来执行此操作。

Once you know the cycle time for your application, you can work out how best to reduce it. You can use the Theory of Constraints to do this by applying the following process.

1. 确定系统的限制条件。这是构建、测试、部署和发布过程中的瓶颈部分。随便挑一个例子,也许就是人工测试的过程。

1. Identify the limiting constraint on your system. This is the part of your build, test, deploy, and release process that is the bottleneck. To pick an example at random, perhaps it’s the manual testing process.

2. 利用约束。这意味着确保您应该最大限度地提高该部分流程的吞吐量。在我们的示例(手动测试)中,您将确保始终有一个故事缓冲区等待手动测试,并确保手动测试所涉及的资源不会被用于其他任何事情。

2. Exploit the constraint. This means ensuring that you should maximize the throughput of that part of the process. In our example (manual testing), you would make sure that there is always a buffer of stories waiting to be manually tested, and ensure that the resources involved in manual testing don’t get used for anything else.

3. 使所有其他过程服从约束。这意味着其他资源不会 100% 工作——例如,如果您的开发人员全力开发故事,等待测试的积压故事将继续成长。相反,让您的开发人员足够努力地工作以保持积压不变,并将剩余时间用于编写自动化测试以捕获错误,从而减少手动测试所需的时间。

3. Subordinate all other processes to the constraint. This implies that other resources will not work at 100%—for example, if your developers work developing stories at full capacity, the backlog of stories waiting to be tested would keep on growing. Instead, have your developers work just hard enough to keep the backlog constant and spend the rest of their time writing automated tests to catch bugs so that less time needs to be spent testing manually.

4.提升约束。如果您的周期时间仍然太长(换句话说,第 2 步和第 3 步的帮助不够),您需要增加可用资源——雇用更多的测试人员,或者可能在自动化测试方面投入更多的精力。

4. Elevate the constraint. If your cycle time is still too long (in other words, steps 2 and 3 haven’t helped enough), you need to increase the resources available—hire more testers, or perhaps invest more effort in automated testing.

5. 冲洗并重复。找到系统的下一个约束并返回到步骤 1。

5. Rinse and repeat. Find the next constraint on your system and go back to step 1.

虽然周期时间是软件交付中最重要的指标,但还有许多其他诊断可以警告您出现问题。这些包括

While cycle time is the most important metric in software delivery, there are a number of other diagnostics that can warn you of problems. These include

• 自动化测试覆盖率

• Automated test coverage

• 代码库的属性,例如重复量、圈复杂度、传出和传入耦合、样式问题等

• Properties of the codebase such as the amount of duplication, cyclomatic complexity, efferent and afferent coupling, style problems, and so on

• 缺陷数

• Number of defects

• 速度,您的团队交付可工作、经过测试、可供使用的代码的速度

• Velocity, the rate at which your team delivers working, tested, ready for use code

• 每天向版本控制系统提交的次数

• Number of commits to the version control system per day

• 每天的构建次数

• Number of builds per day

• 每天的构建失败次数

• Number of build failures per day

• 构建持续时间,包括自动化测试

• Duration of build, including automated tests

图 5.8 Panoptocode 生成的树图显示了 Java 代码库的圈复杂度

Figure 5.8 A tree map generated by Panopticode showing cyclomatic complexity for a Java codebase

图片

值得考虑如何呈现这些指标。上述报告产生了大量数据,解读这些数据是一门艺术。例如,项目经理可能希望看到这些数据经过分析并汇总到一个单一的“健康”指标中,该指标以显示红色、琥珀色或绿色的交通信号灯的形式表示。团队的技术负责人需要更多的细节,但即使是他们也不想费力地阅读一页又一页的报告。我们的同事 Julias Shaw 创建了一个名为 Panoptocode 的项目,该项目针对 Java 代码运行一系列此类报告并生成丰富、密集的可视化效果(如图 5.8) 让您一眼就能看出您的代码库是否存在问题以及问题所在。关键是创建聚合数据的可视化,并以人脑可以最有效地使用其无与伦比的模式匹配技能来识别流程或代码库问题的形式呈现它们。

It is worth considering how these metrics are presented. The reports described above produce a huge amount of data, and interpreting this data is an art. Program managers, for example, might expect to see this data analyzed and aggregated into a single “health” metric that is represented in the form of a traffic light that shows red, amber, or green. A team’s technical lead will want much more detail, but even they will not want to wade through pages and pages of reports. Our colleague, Julias Shaw, created a project called Panopticode that runs a series of these reports against Java code and produces rich, dense visualizations (such as Figure 5.8) that let you see at a glance whether there is a problem with your codebase and where it lies. The key is to create visualizations that aggregate the data and present them in such a form that the human brain can use its unparalleled pattern-matching skills most effectively to identify problems with your process or codebase.

每个团队的持续集成服务器应该在每次签入时生成这些报告和可视化,并将报告存储在您的工件存储库中。然后你应该在数据库中整理结果,并在每个团队中跟踪它们。这些结果应该发布在内部网站上——每个项目都有一个页面。最后,将它们聚合在一起,以便可以在您的开发计划中的所有项目甚至整个组织中监控它们。

Each team’s continuous integration server should generate these reports and visualizations on each check-in, and store the reports in your artifact repository. You should then collate the results in a database, and track them across every team. These results should be published on an internal website—have a page for each project. Finally, aggregate them together so that they can be monitored across all of the projects in your development program, or even your whole organization.

概括

Summary

部署管道的目的是让参与交付软件的每个人都能看到从签入到发布的构建进度。应该可以看到哪些更改破坏了应用程序,哪些导致发布候选版本适合手动测试或发布。您的实施应该能够在手动测试环境中执行按钮部署,并查看这些环境中有哪些候选发布版本。选择发布应用程序的特定版本也应该是一个按钮式任务,可以在完全了解发布的情况下执行正在部署的候选人已成功通过整个管道,因此在类似生产的环境中对其执行了一系列自动化和手动测试。

The purpose of the deployment pipeline is to give everyone involved in delivering software visibility into the progress of builds from check-in to release. It should be possible to see which changes have broken the application and which resulted in release candidates suitable for manual testing or release. Your implementation should make it possible to perform push-button deployments into manual testing environments, and to see which release candidates are in those environments. Choosing to release a particular version of your application should also be a push-button task that can be performed with full knowledge that the release candidate being deployed has passed the entire pipeline successfully, and hence has had a battery of automated and manual tests performed on it in a production-like environment.

一旦实施了部署管道,发布过程中的低效就会变得很明显。各种有用的信息都可以从有效的部署管道中获取,例如发布候选版本需要多长时间才能完成各种手动测试阶段、从签入到发布的平均周期时间以及在哪些阶段发现了多少缺陷在你的过程中。获得这些信息后,您就可以优化构建和发布软件的过程。

Once you have a deployment pipeline implemented, inefficiencies in your release process will become obvious. All kinds of useful information can be derived from a working deployment pipeline, such as how long it takes a release candidate to get through the various manual testing stages, the average cycle time from checkin to release, and how many defects are discovered at which stages in your process. Once you have this information, you can work to optimize your process for building and releasing software.

对于实施部署管道的复杂问题,没有万能的解决方案。关键点是创建一个记录系统来管理从签入到发布的每个更改,提供您在流程中尽早发现问题所需的信息。然后可以使用部署管道的实施来消除流程中的低效率,这样您就可以使反馈周期更快、更强大,也许可以通过添加更多自动化验收测试并更积极地并行化它们,或者通过使您的测试环境更强大类似生产,或通过实施更好的配置管理流程。

There is no one-size-fits-all solution to the complex problem of implementing a deployment pipeline. The crucial point is to create a system of record that manages each change from check-in to release, providing the information you need to discover problems as early as possible in the process. Having an implementation of the deployment pipeline can then be used to drive out inefficiencies in your process so you can make your feedback cycle faster and more powerful, perhaps by adding more automated acceptance tests and parallelizing them more aggressively, or by making your testing environments more production-like, or by implementing better configuration management processes.

反过来,部署管道取决于是否具备一些基础:良好的配置管理、用于构建和部署应用程序的自动化脚本,以及用于证明您的应用程序将为其用户带来价值的自动化测试。它还需要纪律,例如确保只有通过自动构建、测试和部署系统的更改才会发布。我们在第 15 章“管理持续交付”中讨论了这些先决条件和必要的规则,其中包括持续集成、测试、数据管理等的成熟度模型。

A deployment pipeline, in turn, depends on having some foundations in place: good configuration management, automated scripts for building and deploying your application, and automated tests to prove that your application will deliver value to its users. It also requires discipline, such as ensuring that only changes that have passed through the automated build, test, and deployment system get released. We discuss these prerequisites and the necessary disciplines in Chapter 15, “Managing Continuous Delivery,” which includes a maturity model for continuous integration, testing, data management, and so forth.

本书的后续章节将深入探讨有关实施部署管道的更多细节,探讨可能出现的一些常见问题,并讨论可以在此处描述的完整生命周期部署管道的上下文中采用的技术。

The following chapters of the book dive into considerably more detail on implementing deployment pipelines, exploring some of the common issues that may arise and discussing techniques that can be adopted within the context of the full lifecycle deployment pipelines described here.

第 6 章构建和部署脚本

Chapter 6. Build and Deployment Scripting

介绍

Introduction

在非常简单的项目中,可以使用 IDE(集成开发环境)的功能来构建和测试软件。然而,这实际上只适用于最琐碎的任务。一旦项目超出了一个人的范围,跨越了几天,或者产生了不止一个可执行文件作为其输出,如果它不变得复杂和笨重,它就需要更多的控制。在大型或分布式团队(包括开源项目)中工作时,编写构建、测试和打包应用程序的脚本也很重要,否则可能需要数天时间才能让新团队成员启动并运行。

On very simple projects, building and testing the software can be accomplished using the capabilities of your IDE (Integrated Development Environment). However, this is really only appropriate for the most trivial of tasks. As soon as the project extends beyond a single person, spans more than a few days, or produces more than a single executable file as its output, it demands more control if it is not to become complex and unwieldy. It is also vital to script building, testing, and packaging applications when working on large or distributed teams (including open source projects), since otherwise it can take days to get a new team member up and running.

第一步实际上非常简单:几乎每个现代平台都有一种从命令行运行构建的方法。Rails 项目可以运行默认的 Rake 任务;.NET 项目可以使用 MsBuild;Java 项目(如果设置正确)可以使用 Ant、Maven、Buildr、1或摇篮;使用 SCons,运行一个简单的 C/C++ 项目不需要太多。这使得开始持续集成变得简单——只需让您的 CI 服务器运行此命令来创建二进制文件。在许多平台上运行测试也是一个相对简单的过程,只要您使用的是更流行的测试框架之一。Rails 用户和使用 Maven 或 Buildr 的 Java 项目只需运行相关命令即可。.NET 和 C/C++ 用户需要进行一些复制和粘贴操作才能启动和运行。然而,一旦您的项目变得更加复杂——您有多个组件,或者不寻常的打包需求——您将需要卷起袖子投入到构建脚本中。

The first step is actually very simple: Pretty much every modern platform has a way to run the build from the command line. Rails projects can run the default Rake task; .NET projects can use MsBuild; Java projects (if set up correctly) can use Ant, Maven, Buildr,1 or Gradle; and with SCons, not much is required to get a simple C/C++ project going. This makes it straightforward to begin continuous integration—just have your CI server run this command to create binaries. Running tests is also a relatively straightforward process on many platforms, so long as you are using one of the more popular test frameworks. Rails users and Java projects that use Maven or Buildr can just run the relevant command. .NET and C/C++ users will need to do some copy-and-pasting in order to get things up and running. However, once your project gets more complex—you have multiple components, or unusual packaging needs—you’ll need to roll up your sleeves and dive into build scripting.

自动化部署引入了更多的复杂性。将软件部署到测试和生产环境中很少像将单个二进制文件放入生产环境中然后满意地坐下来那样简单。在大多数情况下,它需要一系列步骤,例如配置您的应用程序、初始化数据、配置基础架构、操作系统和中间件、设置模拟外部系统等。随着项目变得越来越复杂,这些步骤变得越来越多、越来越长,并且(如果它们不是自动化的)更容易出错。

Automating deployment introduces further complexities. Deploying software into testing and production environments is rarely as simple as dropping a single binary file into the production environment and sitting back with a satisfied smile. In most cases, it requires a series of steps such as configuring your application, initializing data, configuring the infrastructure, operating systems, and middleware, setting up mock external systems, and so on. As projects get more complex, these steps become more numerous, longer, and (if they are not automated) more error-prone.

使用通用构建工具执行部署在所有情况下都是自找麻烦,除了最简单的情况。可用的部署机制将限于那些支持您的目标环境和中间件的机制。更重要的是,关于如何进行自动化部署的决策应该由开发人员和运维人员共同做出,因为他们都需要熟悉这项技术。

Using general-purpose build tools for performing deployments is asking for trouble in all but the simplest of cases. The available deployment mechanisms will be limited to those supporting your target environment and your middleware. More importantly, decisions on how to do automated deployments should be made by developers and operations personnel together, since both of them will need to be familiar with the technology.

本章旨在向您概述所有构建和部署工具的通用原则、入门信息、一些提示和技巧以及指向更多信息的指针。本章不涉及通过脚本管理环境;这将在第 11 章“管理基础设施和环境”中介绍。我们在本章中没有提供的另一件事是代码示例和工具的详细描述,因为这些很快就会过时。您将在本书的网站 [dzMeNE] 上找到有关可用工具的更多详细信息以及示例脚本。

This chapter aims to give you an overview of the principles common to all build and deployment tools, information to get you going, some tips and tricks, and pointers to more information. We don’t cover managing environments through scripting in this chapter; that will be covered in Chapter 11, “Managing Infrastructure and Environments.” Another thing we don’t supply in this chapter are code examples and detailed descriptions of tools, since these will rapidly become out-of-date. You’ll find much more detail on available tools, along with example scripts, at this book’s website [dzMeNE].

构建和部署系统必须是活生生的东西,不仅能够在初始开发项目期间而且能够作为生产中的可维护软件系统持续存在。因此,必须小心设计和维护它们——像对待其余源代码一样对待它们——并定期进行练习,以便我们知道当我们准备好使用它们时它们是有效的。

Build and deployment systems must be living and breathing things capable of lasting not only during the initial development project but also through its life as a maintainable software system in production. They must therefore be designed and maintained with care—treated the same way you would treat the rest of your source code—and exercised on a regular basis so that we know that they work when we are ready to use them.

构建工具概述

An Overview of Build Tools

图 6.1 一个简单的构建依赖网络

Figure 6.1 A simple build dependency network

图片

很长一段时间以来,自动化构建工具一直是软件开发的一部分。许多人会记得 Make 及其许多变体,它们是使用多年的标准构建工具。所有构建工具都有一个共同的核心:它们允许您对依赖网络进行建模。当你运行你的工具时,它会计算如何通过以正确的顺序执行任务来达到你指定的目标,运行你的目标所依赖的每个任务恰好一次。例如,假设您要运行测试。为此,有必要编译您的代码和测试,并设置您的测试数据。编译任何东西都需要初始化你的环境。图 6.1显示了一个示例依赖网络。

Automated build tools have been a part of software development for a very long time. Many people will remember Make and its many variants that were the standard build tools used for many years. All build tools have a common core: They allow you to model a dependency network. When you run your tool, it will calculate how to reach the goal you specify by executing tasks in the correct order, running each task that your goal depends on exactly once. For example, say you want to run your tests. In order to do this, it’s necessary to compile your code and your tests, and set up your test data. Compiling anything requires initialization of your environment. Figure 6.1 shows an example dependency network.

您的构建工具将计算出它需要执行依赖网络中的每项任务。它可以从 init 或设置测试数据开始,因为这些任务是独立的。一旦完成初始化,它就可以编译源代码或测试——但它必须同时执行这两项操作,并设置测试数据,然后才能运行测试。即使多个目标依赖于 init,它也只会执行一次。

Your build tool will work out that it needs to perform every task in the dependency network. It can start with either init or setting up test data, since these tasks are independent. Once it has done init, it can then compile the source or the tests—but it must do both, and set up the test data, before the tests can be run. Even though multiple targets depend on init, it will only be performed once.

值得注意的一点是,一项任务有两个基本特征:它所做的事情和它所依赖的其他事情。这两个功能在每个构建工具中都有建模。

One small point worth noting is that a task has two essential features: the thing it does and the other things it depends on. These two features are modeled in every build tool.

但是,构建工具在一个方面有所不同:它们是面向任务的还是面向产品的面向任务的构建工具(例如 Ant、NAnt 和 MsBuild)根据一组任务来描述依赖网络。面向产品的工具,例如 Make,根据它们生成的产品(例如可执行文件)来描述事物。

However, there is one area in which build tools differ: whether they are taskoriented or product-oriented. Task-oriented build tools (for example, Ant, NAnt, and MsBuild) describe the dependency network in terms of a set of tasks. A product-oriented tool, such as Make, describes things in terms of the products they generate, such as an executable.

乍一看,这种区别似乎有点学术性,但对于理解如何优化构建和确保构建过程的正确性来说很重要。例如,构建工具必须确保对于给定的目标,每个先决条件必须恰好执行一次。如果缺少先决条件,构建过程的结果将很糟糕。如果先决条件被执行多次,最好的情况是构建将花费更长的时间(如果先决条件是幂等的),最坏的情况是构建过程的结果再次不好。

This distinction appears somewhat academic at first glance, but it is important in order to understand how to optimize builds and to ensure the correctness of build processes. For instance, a build tool must ensure that for a given goal, each prerequisite must be executed exactly once. If a prerequisite is missed, the result of the build process will be bad. If a prerequisite is executed more than once, the best case scenario is that the build will take longer (if the prerequisite is idempotent), and the worst case is that the result of the build process is again bad.

通常,构建工具将遍历整个网络,调用(但不一定执行)每个任务。因此,在我们的示例中,我们假设的构建工具可能会调用Set up test dataInitCompile sourceInitCompile testsRun tests任务。在面向任务的工具中,每个任务都会知道它是否已经作为构建的一部分被执行。因此,即使Init任务被调用两次,它也只会执行一次。

Typically, build tools will walk the entire network, invoking (but not necessarily executing) each task. So our hypothetical build tool might invoke, in our example, the Set up test data, Init, Compile source, Init, Compile tests, and then Run tests tasks. In a task-oriented tool, each task will know whether or not it has already been executed as part of the build. Thus, even though the Init task is invoked twice, it would only be executed once.

然而,在面向产品的工具中,世界被建模为一组文件。因此,例如,我们示例中的Compile sourceCompile tests目标将各自生成一个包含所有已编译代码的文件——我们称它们为 source.so 和 tests.so。Run tests目标反过来生成一个名为 testreports.zip 的文件面向产品的构建系统将确保它在Compile sourceCompile tests之后调用Run tests,但是Run tests目标将仅当任一 .so 文件上的时间戳晚于 testreports.zip 上的时间戳时才真正执行。

However, in a product-oriented tool, the world is modeled as a set of files. So, for example, the Compile source and Compile tests goals in our example would each result in a single file that contains all the compiled code—let’s call them source.so and tests.so. The Run tests target, in turn, generates a file called testreports.zip. A product-oriented build system would ensure it invoked Run tests after Compile source and Compile tests, but the Run tests target would only actually be executed if the timestamp on either of the .so files is later than the timestamp on testreports.zip.

因此,面向产品的构建工具以时间戳的形式在每个任务生成的文件上保持它们的状态(SCons 使用 MD5 签名)。这在您编译 C 或 C++(例如)时非常有用,因为 Make 将确保您只编译那些自上次运行构建以来发生更改的源代码文件。此功能称为增量构建,与大型项目的干净构建相比可以节省数小时。在 C/C++ 中编译需要相对较长的时间,因为编译器必须做大量的优化代码的工作。在虚拟机上运行的语言中,编译器只创建字节码,虚拟机的即时 (JIT) 编译器在运行时进行优化。

Product-oriented build tools thus keep their state in the form of timestamps on the files generated by each of the tasks (SCons uses MD5 signatures). This is great when you’re compiling C or C++ (for example), because Make will ensure that you only compile those source code files that have changed since the last time the build was run. This feature, known as an incremental build, can save hours over a clean build on large projects. Compilation takes a comparatively long time in C/C++ because the compilers have to do a great deal of work optimizing the code. In languages that run on a virtual machine, the compiler just creates the byte code, and the virtual machine’s just-in-time (JIT) compiler does the optimization at run time.

相比之下,面向任务的构建工具在构建之间不保持任何状态。这使得它们不那么强大并且完全不适合编译 C++。但是,它们对于 C# 等语言工作得很好,因为这些语言的编译器具有用于执行增量构建的内置逻辑。2最后,值得注意的是,Rake 既可以用作面向产品的工具,也可以用作面向任务的工具。有关依赖网络的更多信息,请参阅Martin Fowler [8ZKox1] 的领域特定语言

Task-oriented build tools, in contrast, keep no state between builds. This makes them less powerful and entirely unsuitable for compiling C++. However, they work fine for languages such as C# since the compilers for these languages have built-in logic for performing incremental builds.2 Finally, it is worth noting that Rake can function either as a product-oriented or a task-oriented tool. For more on dependency networks, refer to Domain-Specific Languages by Martin Fowler [8ZKox1].

我们现在将对当前的构建工具进行一个简短的调查。同样,您可以在本书的网站 [dzMeNE] 上找到使用其中许多技术构建脚本的示例以及更多参考资料。

We’ll now take a brief survey of current build tools. Again, you can find examples of build scripts using many of these technologies, and further references, at this book’s website [dzMeNE].

制作

Make

Make 及其变体在系统开发领域仍然很强大。它是一个强大的面向产品的构建工具,能够跟踪构建中的依赖关系并仅构建那些受特定更改影响的组件。当编译时间是开发周期中的重要成本时,这对于优化开发团队的性能至关重要。

Make and its variants are still going strong in the world of systems development. It is a powerful product-oriented build tool capable of tracking dependencies within a build and building only those components that are affected by a particular change. This is essential in optimizing the performance of a development team when compile time is a significant cost in the development cycle.

不幸的是,Make 有很多缺点。随着应用程序变得越来越复杂并且它们的组件之间的依赖性数量增加,Make 中内置的规则的复杂性意味着它们变得难以调试。

Unfortunately, Make has a number of drawbacks. As applications become more complex and the number of dependencies between their components increases, the complexity of the rules built into Make means that they become hard to debug.

为了降低这种复杂性,处理大型代码库的团队通常采用的惯例是为每个目录创建一个 Makefile,并有一个顶层 Makefile 递归地运行每个子目录中的 Makefile。这意味着构建信息和过程最终可能会分布在许多文件中。当有人签入对构建的更改时,可能很难弄清楚到底发生了什么变化以及它将如何影响最终的可交付成果。

To tame some of this complexity, a common convention adopted by teams working on large codebases is to create a Makefile for each directory, and have a top-level Makefile that recursively runs the Makefiles in each subdirectory. This means that build information and processes can end up spread over many files. When someone checks in a change to the build, it can be quite hard to work out what exactly has changed and how it will affect the final deliverables.

由于在某些情况下空格可能很重要,因此 Makefile 也容易受到一类非常难以发现的错误的影响。例如,在命令脚本中,要传递给 shell 的命令必须以制表符开头。如果改用空格,脚本将无法运行。

Makefiles are also susceptible to a very hard-to-find class of bugs due to the fact that whitespace can be significant under certain circumstances. For example, within a command script, commands to be passed to the shell must be prefaced with a tab. If spaces are used instead, the script will not work.

Make 的另一个缺点是它依赖于 shell 来实际做任何事情。因此,Makefile 是特定于操作系统的(实际上,围绕 Make 的工具链已经进行了大量工作,以使构建能够跨各种 UNIX 风格运行)。由于 Makefile 是一种外部 DSL,它不提供对核心系统的扩展(除了定义新规则),任何扩展都必须在不访问 Make 内部数据结构的情况下重新发明通用解决方案。

Another disadvantage of Make is that it relies on the shell to actually do anything. As a result, Makefiles are specific to an operating system (indeed, a great deal of work has gone into the toolchain around Make to enable builds to work across the various UNIX flavors). Since Makefiles are an external DSL which doesn’t provide for extensions to the core system (apart from defining new rules), any extensions must reinvent common solutions without access to Make’s internal data structures.

这些问题,加上 Make 应用程序根部的声明式编程模型对大多数开发人员(他们通常更习惯命令式编程)并不熟悉,意味着 Make 很少被用作新开发的主要构建工具商业应用。

These problems, combined with the fact that the declarative programming model at the root of a Make application is not familiar to most developers (who are often more comfortable with imperative programming), mean that Make is rarely used as a primary build tool in newly developed commercial applications.

“这是我对软件不满意的那些日子之一。有时令我惊讶的是,那些日子中有多少涉及 Make。”——Mark Dominus,“遭受‘make install’的困扰”[dyGIMy]。

“This is one of those days when I am not happy with software. It sometimes surprises me how many of those days involve Make.”—Mark Dominus, “Suffering from ‘make install’” [dyGIMy].

如今,许多 C/C++ 开发人员都优先使用 SCons 而不是 Make。SCons 本身及其构建文件是用 Python 编写的。这使它成为比 Make 更强大、更便携的工具。它包括许多有用的功能,例如支持 Windows 开箱即用和并行构建。

These days, many C/C++ developers are using SCons in preference to Make. SCons itself and its build files are written in Python. This makes it a much more powerful and portable tool than Make. It includes many useful features such as supporting Windows out of the box and parallelized builds.

蚂蚁

Ant

随着 Java 开发人员的出现,开始进行更多的跨平台开发。Make 固有的局限性变得更加痛苦。作为回应,Java 社区尝试了几种解决方案,首先是将 Make 本身移植到 Java。与此同时,XML 作为一种构建结构化文档的便捷方式开始崭露头角。这两种方法融合在一起并产生了 Apache Ant 构建工具。

With the emergence of Java developers started to do more cross-platform development. The limitations inherent in Make became more painful. In response, the Java community experimented with several solutions, at first porting Make itself to Java. At the same time, XML was coming to prominence as a convenient way to build structured documents. These two approaches converged and resulted in the Apache Ant build tool.

Ant 完全跨平台,包括一组用 Java 编写的任务,用于执行编译和文件系统操作等常见操作。Ant 可以通过用 Java 编写的新任务轻松扩展。Ant 迅速成为 Java 项目事实上的标准构建工具。它现在得到 IDE 和其他工具的广泛支持。

Fully cross-platform, Ant includes a set of tasks written in Java to perform common operations such as compilation and filesystem manipulation. Ant can be easily extended with new tasks written in Java. Ant quickly became the de facto standard build tool for Java projects. It is now widely supported by IDEs and other tools.

Ant 是一个面向任务的构建工具。Ant 的运行时组件是用 Java 编写的,但 Ant 脚本是用 XML 编写的外部 DSL。这种结合赋予了 Ant 强大的跨平台能力。它也是一个极其灵活和强大的系统,Ant 任务可以完成您可能想做的大多数事情。

Ant is a task-oriented build tool. The runtime components of Ant are written in Java, but the Ant scripts are an external DSL written in XML. This combination gives Ant powerful cross-platform capabilities. It is also an extremely flexible and powerful system, with Ant tasks for most things you could want to do.

然而,Ant 有几个缺点:

However, Ant suffers from several shortcomings:

• 您需要用XML 编写您的构建脚本,这既不简洁也不适合人类阅读。

• You need to write your build scripts in XML, which is neither succinct nor pleasant for humans to read.

• Ant 有一个贫血领域模型。除了任务之外没有真正的领域概念,这意味着您必须花费大量时间编写样板文件来编译、创建 JAR、运行测试等。

• Ant has an anaemic domain model. There are no real domain concepts over and above a task, which means you have to spend a great deal of time writing boilerplate to compile, create JARs, run tests, and so forth.

• Ant 是一种声明式语言,而不是命令式语言。然而,只有足够的命令式标签(例如可怕的<antcall>)允许用户混合他们的隐喻并制造不愉快和混乱。

• Ant is a declarative language, not an imperative one. However, there are just enough imperative-style tags (such as the dreaded <antcall>) to allow users to mix their metaphors and create unpleasantness and confusion all round.

• 您不能轻易提出有关 Ant 任务的问题,例如“运行了多少测试?” 和“他们花了多长时间?” 您所能做的就是让一个工具将此信息输出到命令行,以便您可以解析它,或者通过编写自定义 Java 代码来检测它来连接到 Ant 的内部结构。

• You cannot easily ask questions about Ant tasks, such as “How many tests ran?” and “How long did they take?” All you can do is have a tool print this information out to the command line so you can parse it, or hook into Ant’s internals by writing custom Java code to instrument it.

• 虽然 Ant 通过导入macrodef任务支持重用,但新手用户对这些任务了解甚少。

• While Ant supports reuse through the import and macrodef tasks, these are poorly understood by novice users.

由于这些限制,Ant 文件往往很长且分解很差 — Ant 文件有数千行长并不罕见。使用 Ant 文件时,Julian Simpson 在The ThoughtWorks Anthology中的文章“Refactoring Ant Build Files”是一个非常宝贵的资源

As a result of these limitations, Ant files tend to be long and poorly factored—it is not unusual for Ant files to be thousands of lines long. An invaluable resource when working with Ant files is Julian Simpson’s article “Refactoring Ant Build Files,” in The ThoughtWorks Anthology.

NAnt 和 MSBuild

NAnt and MSBuild

当 Microsoft 首次推出 .NET 框架时,它具有许多与 Java 语言和环境相同的特性。在这个新平台上工作的 Java 开发人员迅速移植了一些他们最喜欢的开源 Java 工具。因此,我们使用的不是 JUnit 和 JMock,而是 NUnit 和 NMock——而且,不出所料,还有 NAnt。NAnt 使用与 Ant 基本相同的语法,只有一些不同之处。

When Microsoft first introduced the .NET framework, it had many features in common with the Java language and environment. Java developers who worked on this new platform quickly ported some of their favorite open source Java tools. So instead of JUnit and JMock we have NUnit and NMock—and, rather predictably, NAnt. NAnt uses essentially the same syntax as Ant, with only a few differences.

微软后来在 NAnt 上引入了他们自己的小变体,并将其称为 MSBuild。它是 Ant 和 NAnt 的直接后代,任何使用过这些工具的人都会熟悉它。但是,它更紧密地集成到 Visual Studio 中,了解如何构建 Visual Studio 解决方案和项目以及如何管理依赖项(因此,NAnt 脚本经常调用 MSBuild 进行编译)。虽然一些用户抱怨 MSBuild 提供的灵活性不如 NAnt,但它更定期更新并作为 .NET 框架的一部分发布这一事实使 NAnt 成为了一个利基市场。

Microsoft later introduced their own minor variation on NAnt and called it MSBuild. It is a direct descendant of Ant and NAnt, and will be familiar to anyone who has used those tools. However, it is more tightly integrated into Visual Studio, understanding how to build Visual Studio solutions and projects and how to manage dependencies (as a result, NAnt scripts often call out to MSBuild to do compilation). While some users complain that MSBuild provides less flexibility than NAnt, the fact that it is more regularly updated and ships as part of the .NET framework has made NAnt a niche player.

它们都受到上述 Ant 的许多限制。

Both of them suffer from many of the limitations of Ant described above.

行家

Maven

有一段时间,Ant 在 Java 社区中无处不在——但创新并没有就此止步。Maven 试图通过拥有一个更复杂的域来删除 Ant 文件中发现的大量样板文件,该域对 Java 项目的布局方式做出许多假设。这种约定优于配置的原则意味着,只要您的项目符合 Maven 规定的结构,它将执行几乎所有您可以用单个命令想象的构建、部署、测试和发布任务,而无需编写更多而不是几行 XML。这包括为您的项目创建一个默认托管应用程序 Javadoc 的网站。

For a time, Ant was ubiquitous in the Java community—but innovation did not stop there. Maven attempts to remove the large amount of boilerplate found in Ant files by having a more complex domain that makes many assumptions about the way your Java project is laid out. This principle of favoring convention over configuration means that, so long as your project conforms to the structure dictated by Maven, it will perform almost any build, deploy, test, and release task you can imagine with a single command, without having to write more than a few lines of XML. That includes creating a website for your project which hosts your application’s Javadoc by default.

Maven 的另一个重要特性是它支持自动化管理 Java 库和项目之间的依赖关系,这是大型 Java 项目的痛点之一。Maven 还支持复杂但严格的软件分区方案,允许您将复杂的解决方案分解为更小的组件。

The other important feature of Maven is its support for automated management of Java libraries and dependencies between projects, a problem that is one of the pain points in large Java projects. Maven also supports a complex but rigid software partitioning scheme that allows you to decompose complex solutions into smaller components.

Maven 的问题是三方面的。首先,如果您的项目不符合 Maven 对结构和生命周期的假设,那么要让 Maven 做您想要的事情可能会非常困难(甚至不可能)。在一些商店中,这被认为是一个特性——它迫使开发团队根据 Maven 的指令来构建他们的项目。对于没有经验的开发商店,或者如果您有大量项目,这可能是一件好事。但是如果你想做一些稍微偏离常规的事情(比如在执行测试之前加载一些自定义测试数据),你将不得不颠覆 Maven 的生命周期和领域模型——这是一种非常痛苦且无法维护的行动过程,但是往往是不可避免的。Ant 比 Maven 灵活得多。

The problem with Maven is threefold. First of all, if your project doesn’t conform to Maven’s assumptions about structure and lifecycle, it can be extremely hard (or even impossible) to make Maven do what you want. In some shops this is considered a feature—it forces development teams to structure their projects according to Maven’s dictates. This can be a good thing with an inexperienced development shop, or if you have a large number of projects. But if you want to do things even slightly off the beaten track (such as loading some custom test data before performing a test), you will have to subvert Maven’s lifecycle and domain model—an intensely painful and unmaintainable course of action, but one that is often inevitable. Ant is far more flexible than Maven.

Maven 的第二个问题是它还使用了一个用 XML 编写的外部 DSL,这意味着为了扩展它,你需要编写代码。虽然编写 Maven 插件并不是特别复杂,但它不是您可以在几分钟内完成的;在阅读本文时,您需要了解 Mojos、插件描述符以及 Maven 使用的任何反转控制框架。幸运的是,Maven 拥有几乎所有您希望在普通 Java 项目中执行的操作的插件。

Maven’s second problem is that it also uses an external DSL written in XML, which means that in order to extend it, you need to write code. While writing a Maven plugin is not inordinately complex, it is not something you can just knock out in a few minutes; you’ll need to learn about Mojos, plugin descriptors, and whatever inversion-of-control framework Maven is using by the time you read this. Fortunately, Maven has plugins for almost everything you’d want to do in the average Java project.

Maven 的第三个问题是,在其默认配置中,它是自我更新的。Maven的核心很小,为了让自己发挥功能,它从网上下载了自己的插件。Maven 每次运行时都会尝试升级自身,并且由于其中一个插件的升级或降级,它可能会意外失败。也许更严重的是,这意味着您无法重现您的构建。一个相关的问题是,Maven 的库和依赖管理功能允许使用跨项目使用的组件快照,如果使用快照依赖项,这再次使再现特定构建变得困难。

The third problem with Maven is that, in its default configuration, it is selfupdating. Maven’s core is very small, and in order to make itself functional, it downloads its own plugins from the Internet. Maven will attempt to upgrade itself every time it is run and, as a result of an upgrade or downgrade of one of its plugins, it can fail unpredictably. Perhaps more seriously, it means that you can’t reproduce your builds. A related problem is that Maven’s library and dependency management functionality allows for the use of snapshots of components to be used across projects, which again makes it hard to reproduce a particular build if it uses snapshot dependencies.

对于一些团队来说,Maven 的约束可能过于严重,或者需要花费太多精力来重构他们的构建以匹配 Maven 的假设。因此,他们坚持使用 Ant。最近,创建了一个名为 Ivy 的工具,让您无需使用 Maven 即可管理库和组件之间的依赖关系。如果您出于某种原因依赖于使用 Ant,那么这使得获得 Maven 的一些好处成为可能。

For some teams, the constraints of Maven may be too serious, or it would take too much effort to restructure their build to match Maven’s assumptions. As a result, they have stuck with Ant. More recently, a tool called Ivy has been created that lets you manage libraries and dependencies between components without having to use Maven. This makes it possible to gain some of the benefits of Maven if you are, for some reason, tied to using Ant.

请注意,虽然 Ivy 和 Maven 非常擅长管理组件之间的依赖关系,但它们管理外部依赖关系的默认机制——从 Maven 社区维护的 Internet 存档中下载它们——并不总是最佳选择。首先,有一个传说中的问题,即第一次开始构建会导致等待 Maven 下载一半的 Internet。更有问题的是,除非您对所使用的每个依赖项的版本非常自律,否则由于 Maven 在您不注意的情况下更改某些库的版本,很容易导致菱形依赖项问题和损坏。

Note that while Ivy and Maven are great at managing dependencies between components, their default mechanism for managing external dependencies—downloading them from an Internet archive maintained by the Maven community—is not always the best choice. For a start, there’s the fabled problem where kicking off your build for the first time leads to waiting for Maven to download half of the Internet. More problematically, unless you are very disciplined about which version of each dependency you are using, it is easy to end up with diamond dependency problems and breakages due to Maven changing a version of some library without your noticing.

有关管理组件之间的库和依赖关系的更多信息,请参阅第 13 章“管理组件和依赖关系”。

For more on managing libraries and dependencies between components, refer to Chapter 13, “Managing Components and Dependencies.”

Rake

Ant 及其同类语言是用于构建软件的外部特定领域语言 (DSL)。然而,他们选择 XML 来表示这些语言使得它们难以创建、读取、维护和扩展。主要的 Ruby 构建工具 Rake 是作为一项实验出现的,目的是看看 Make 的功能是否可以通过在 Ruby 中创建内部 DSL 来轻松复制。答案是肯定的,Rake 就诞生了。Rake 是类似于 Make 的面向产品的工具,但它也可以用作面向任务的工具。

Ant and its brethren are external domain-specific languages (DSLs) for building software. However, their choice of XML to represent these languages made them hard to create, read, maintain, and extend. The dominant Ruby build tool, Rake, came about as an experiment to see if Make’s functionality could be easily reproduced by creating an internal DSL in Ruby. The answer was “yes,” and Rake was born. Rake is a product-oriented tool similar to Make, but it can also be used as a task-oriented tool.

和 Make 一样,Rake 除了任务和依赖关系之外,对任何东西都不了解。然而,由于 Rake 脚本是普通的 Ruby,您可以使用 Ruby 的 API 来执行您想要的任何任务。因此,在 Rake 中创建强大的、独立于平台的构建文件非常简单:您可以随意使用通用编程语言的所有原生功能。

Like Make, Rake has no understanding of anything except tasks and dependencies. However, since Rake scripts are plain Ruby, you can use Ruby’s API to carry out whatever tasks you want. As a result, creating powerful, platformindependent build files is straightforward in Rake: You have all the native power of a general-purpose programming language at your disposal.

当然,使用通用语言意味着您在维护构建脚本时可以使用在正常开发中可用的所有工具。您可以重构和模块化您的构建,并且可以使用您的常规开发环境。使用标准的 Ruby 调试器调试 Rake 非常简单。如果您在执行 Rake 构建脚本时遇到错误,您将获得堆栈跟踪以帮助您了解出了什么问题。实际上,由于 Ruby 中的类是开放扩展的,因此您可以从构建脚本中向 Rake 的类添加方法以进行调试目的。Martin Fowler 的 bliki 条目“使用 Rake 构建语言”[9lfL15] 中描述了这个和 Rake 的许多其他有用技术。

Of course the use of a general-purpose language means that all of the tools available to you in normal development are available to you when you are maintaining your build scripts. You can refactor and modularize your builds, and you can use your regular development environment. It is straightforward to debug Rake using the standard Ruby debugger. If you hit a bug in the execution of your Rake build script, you will get a stack trace to help you understand what has gone wrong. Indeed, since classes in Ruby are open for extension, you can add methods to Rake’s classes from within your build script for debugging purposes. This and many other useful techniques for Rake are described in Martin Fowler’s bliki entry “Using the Rake Build Language” [9lfL15].

仅仅因为 Rake 是由 Ruby 程序员开发并广泛用于 Ruby 项目并不意味着它不能用于使用其他技术的项目(例如,Albacore 项目提供了一组用于构建 .NET 系统的 Rake 任务) . Rake 是一个通用的构建脚本工具。当然,您的开发团队需要具备(或获得)一些基本的 Ruby 编程技能,但对于 Ant 或 NAnt,您也可以这样说。

Just because Rake was developed by Ruby programmers and is widely used for Ruby projects doesn’t mean it can’t be used for projects that use other technologies (for example, the Albacore project provides a set of Rake tasks for building .NET systems). Rake is a general-purpose build scripting tool. Of course, your development team will need to have (or acquire) some basic programming skills in Ruby, but you can say the same for Ant or NAnt.

Rake 有两个普遍的缺点:首先,您必须确保在您的平台上有一个合适的运行时可用(JRuby 作为最便携和可靠的平台正在迅速获得发展势头);其次,您必须与 RubyGems 进行交互。

There are two general disadvantages of Rake: first, you have to ensure that a decent runtime is available on your platform (JRuby is rapidly gaining momentum as the most portable and reliable platform); and second, you have to interact with RubyGems.

建设者

Buildr

Rake 的简单性和强大功能使构建脚本应该用真正的编程语言编写成为一个令人信服的案例。新一代的构建工具,例如 Buildr、Gradle 和 Gantt,都采用了这种方法。它们都具有用于构建软件的内部 DSL。然而,他们试图让依赖管理和多项目构建等更复杂的挑战变得简单。我们将更详细地讨论 Buildr,因为它是我们最熟悉的。

The simplicity and power of Rake makes a compelling case that build scripts should be written in a real programming language. The new generation of build tools, such as Buildr, Gradle, and Gantt, have taken this approach. They all feature internal DSLs for building software. However, they attempt to make the more complex challenges of dependency management and multiproject builds just as easy. We’ll discuss Buildr in more detail since it’s the one we’re most familiar with.

Buildr 建立在 Rake 之上,因此您可以在 Rake 中做的所有事情都可以继续在 Buildr 中做。然而,Buildr 也是 Maven 的直接替代品——它使用与 Maven 相同的约定,包括文件系统布局、工件规范和存储库。它还允许您以零配置使用 Ant 任务(包括任何自定义任务)。它利用 Rake 的面向产品的框架来进行增量构建。令人惊讶的是,它也比 Maven 更快。然而,与 Maven 不同的是,自定义任务和创建您自己的新任务非常简单。

Buildr is built on top of Rake, so everything you can do in Rake you can continue to do in Buildr. However, Buildr is also a drop-in replacement for Maven—it uses the same conventions that Maven does, including filesystem layout, artifact specifications, and repositories. It also lets you use Ant tasks (including any custom ones) with zero configuration. It leverages Rake’s product-oriented framework to do incremental builds. Astonishingly, it is also faster than Maven. However, unlike Maven, it is extremely simple to customize tasks and create new ones of your own.

如果您正在开始一个新的 Java 项目,或者正在寻找 Ant 或 Maven 的替代品,我们强烈建议您考虑使用 Buildr 或 Gradle(如果您更喜欢 Groovy 中的 DSL)。

If you’re starting a new Java project, or looking for a replacement for Ant or Maven, we strongly suggest you consider Buildr, or Gradle if you prefer your DSLs in Groovy.

圣杯

Psake

Windows 用户不必错过新一波的内部 DSL 构建工具。Psake 发音为“saké”,是一种用 PowerShell 编写的内部 DSL,它提供面向任务的依赖网络。

Windows users need not miss out on the new wave of internal DSL build tools. Pronounced “saké,” Psake is an internal DSL written in PowerShell, which provides task-oriented dependency networks.

构建和部署脚本的原则和实践

Principles and Practices of Build and Deployment Scripting

在本节中,我们将列出构建和部署脚本的一些一般原则和实践,无论您使用哪种技术,它们都应该适用。

In this section, we’ll lay out some general principles and practices of build and deployment scripting which should apply whichever technology you use.

为部署管道中的每个阶段创建脚本

Create a Script for Each Stage in Your Deployment Pipeline

我们是领域驱动设计的忠实拥护者,3并将这些技术应用到我们创建的任何软件的设计中。当我们设计构建脚本时,这没有什么不同。这也许有点夸张地说我们希望我们的构建脚本的结构清楚地表示它们正在实施的过程。采用这种方法可确保我们的脚本具有定义明确的结构,这有助于我们在维护期间保持它们的清洁,并最大限度地减少构建和部署系统组件之间的依赖性。幸运的是,部署管道提供了一个很好的组织原则来划分构建脚本之间的职责。

We are big fans of domain-driven design,3 and apply these techniques in the design of any software that we create. This is no different when we design our build scripts. That is perhaps a bit of a grandiose way of saying that we want the structure of our build scripts to clearly represent the processes that they are implementing. Taking this approach ensures that our scripts have a well-defined structure that helps us to keep them clean during maintenance and minimizes dependencies between components of our build and deployment system. Luckily, the deployment pipeline provides an excellent organizing principle for dividing up responsibilities between build scripts.

当您第一次启动您的项目时,使用一个脚本来包含将在执行部署管道的过程中执行的每个操作,以及尚未自动化的步骤的虚拟目标是有意义的。但是,一旦您的脚本足够长,您就可以将它分成单独的脚本,用于管道中的每个阶段。因此,您将拥有一个提交脚本,其中包含编译应用程序、打包应用程序、运行提交测试套件和执行代码静态分析所需的所有目标。4然后,您需要一个功能验收测试脚本,它调用您的部署工具将应用程序部署到适当的环境,然后准备任何数据,最后运行验收测试。您还可以有一个脚本来运行任何非功能性测试,例如压力测试或安全测试。

When you first start your project, it makes sense to have a single script containing every operation that will be performed in the course of executing the deployment pipeline, with dummy targets for steps that are not yet automated. However, once your script gets sufficiently long, you can divide it up into separate scripts for each stage in your pipeline. Thus you will have a commit script containing all the targets required to compile your application, package it, run the commit test suite, and perform static analysis of the code.4 You then need a functional acceptance test script that calls your deployment tool to deploy the application to the appropriate environment, then prepares any data, and finally runs the acceptance tests. You could also have a script that runs any nonfunctional tests such as stress tests or security tests.


图片

确保将所有脚本保存在版本控制存储库中,最好是源代码所在的同一存储库。开发人员和运维人员能够协作构建和部署脚本,并将它们保存在同一存储库中,这一点至关重要是什么使这成为可能。

Make sure you keep all your scripts in a version control repository, preferably the same one that your source code lives in. It is essential for developers and operations people to be able to collaborate on build and deployment scripts, and keeping them in the same repository is what enables this.


使用适当的技术来部署您的应用程序

Use an Appropriate Technology to Deploy Your Application

在典型的部署管道中,成功提交阶段之后的大多数阶段(例如自动验收测试阶段和用户验收测试阶段)取决于将应用程序部署到类似生产的环境中。这种部署也是自动化的,这一点至关重要。但是,在自动化部署时,您应该使用适合该工作的工具,而不是通用脚本语言(除非部署过程非常简单)。几乎每个常见的中间件都有用于配置和部署的工具,因此您应该使用这些工具。例如,如果您正在使用 WebSphere Application Server,您将希望使用 Wsadmin 工具来配置容器和部署应用程序。

In a typical deployment pipeline, most stages that follow a successful commit stage, such as the automated acceptance test stage and user acceptance test stage, depend upon the application being deployed to a production-like environment. It is vital that this deployment is automated too. However, you should use the right tool for the job when automating deployment, not a general-purpose scripting language (unless the deployment process is extremely simple). Pretty much every common piece of middleware has tools for configuring it and deploying to it, so you should use those. If you’re using the WebSphere Application Server, for example, you’ll want to use the Wsadmin tool to configure the container and deploy the application.

最重要的是,您的应用程序的部署将由开发人员(在他们的本地计算机上,如果没有其他地方)以及测试人员和操作人员完成。因此,关于如何部署应用程序的决定需要涉及所有这些人。它还需要在项目开始时发生。

Most importantly, deployment of your application will be done both by developers (on their local machines, if nowhere else) and by testers and operations staff. Thus, the decision on how to deploy your application needs to involve all of these people. It also needs to happen towards the start of the project.

部署脚本应涵盖升级应用程序以及从头开始安装的情况。这意味着,例如,它应该在部署之前关闭以前运行的应用程序版本,并且它应该能够从头开始创建任何数据库以及升级现有数据库。

The deployment script should cover the case of upgrading your application as well as installing it from scratch. That means, for example, that it should shut down previously running versions of the application before deploying, and it should be able to create any database from scratch as well as upgrade an existing one.

使用相同的脚本部署到每个环境

Use the Same Scripts to Deploy to Every Environment

第 113 页的“部署管道实践”部分所述,必须使用相同的流程部署到您的应用程序运行的每个环境,以确保构建和部署流程得到有效测试。这意味着使用相同的脚本部署到每个环境并表示环境之间的差异——例如服务 URI 和 IP地址——作为单独管理的配置信息。从脚本中分离出配置信息并将其存储在版本控制中,为您的部署脚本提供某种机制来检索它,如第 2 章“配置管理”中所述。

As described in the “Deployment Pipeline Practices” section on page 113, it is essential to use the same process to deploy to every environment in which your application runs to ensure that the build and deployment process is tested effectively. That means using the same scripts to deploy to each environment and representing the differences between environments—such as service URIs and IP addresses—as configuration information to be managed separately. Separate out configuration information from the script and store it in version control, providing some mechanism for your deployment script to retrieve it as described in Chapter 2, “Configuration Management.”

构建和部署脚本都必须在开发人员的机器以及类似生产的环境中工作,并且它们用于执行开发人员的所有构建和部署活动。一个只有开发人员使用的并行构建系统很容易出现——但这消除了保持构建和部署脚本灵活、分解良好和测试良好的关键力量之一。如果您的应用程序依赖于内部开发的其他组件,您将希望确保可以轻松地将正确的版本(即已知可以可靠地协同工作的版本)安装到开发人员的机器上。这是 Maven 和 Ivy 等工具可以派上用场的领域。

It is essential that both build and deployment scripts work on developers’ machines as well as on production-like environments, and that they are used to perform all build and deployment activities by developers. It is all too easy for a parallel build system to spring up that only the developers use—but this removes one of the key forces keeping build and deployment scripts flexible, well factored, and well tested. If your application depends on other components developed inhouse, you’ll want to make sure it’s easy to get the right versions—meaning ones that are known to work reliably together—onto developer machines. This is one area where tools like Maven and Ivy come in very handy.

如果您的应用程序在其部署架构方面很复杂,您将必须进行一些简化才能使其在开发人员机器上运行。这可能涉及大量工作,例如在部署时用内存数据库替换 Oracle 集群的能力。然而,这种努力肯定会得到回报。当开发人员不得不依赖共享资源来运行应用程序时,它的运行频率必然低得多,反馈循环也慢得多。反过来,这会导致更多的缺陷和更慢的开发速度。问题不是“我们如何证明成本合理?” 而是,“我们如何证明不投资使应用程序在本地运行是合理的?”

If your application is complex in terms of its deployment architecture, you will have to make some simplifications to get it working on developer machines. This may involve significant work, such as the ability to replace an Oracle cluster with an in-memory database at deploy time. However, this effort will certainly pay back. When developers have to rely on shared resources in order to run the application, it is necessarily run much less frequently, and the feedback loop is much slower. This, in turn, leads to more defects and a slower pace of development. The question is not “How can we justify the cost?” but rather, “How can we justify not investing in making the application run locally?”

使用操作系统的打包工具

Use Your Operating System’s Packaging Tools

在本书中,我们使用术语“二进制文件”作为应用程序部署过程中放置​​在目标环境中的对象的统称。大多数时候,这是由您的构建过程创建的一堆文件、您的应用程序需要的任何库,也许还有另一组签入版本控制的静态文件。

We use the term “binaries” throughout this book as a catch-all term for the objects that you put on your target environments as part of your application’s deployment process. Most of the time, this is a bunch of files created by your build process, any libraries your application requires, and perhaps another set of static files checked into version control.

然而,部署一堆需要跨文件系统分布的文件效率非常低,并且使维护(以升级、回滚和卸载的形式)非常痛苦。这就是发明包装系统的原因。如果您的目标是单个操作系统或一小组相关操作系统,我们强烈建议使用该操作系统的打包技术来捆绑需要部署的所有内容。例如,Debian 和 Ubuntu 都使用 Debian 包系统;RedHat、SuSE 和许多其他类型的 Linux 使用 RedHat 包系统;Windows用户可以使用Microsoft Installer系统等。所有这些打包系统都相对简单易用,并且有很好的工具支持。

However, deploying a bunch of files that need to be distributed across the filesystem is very inefficient and makes maintenance—in the form of upgrades, rollbacks, and uninstalls—extremely painful. This is why packaging systems were invented. If you are targeting a single operating system, or a small set of related operating systems, we strongly recommend using that OS’s packaging technology to bundle up everything that needs to be deployed. For example, Debian and Ubuntu both use the Debian package system; RedHat, SuSE, and many other flavors of Linux use the RedHat package system; Windows users can use the Microsoft Installer system, and so forth. All of these packaging systems are relatively simple to use and have great tool support.

每当您的部署涉及跨文件系统散布文件或向注册表添加密钥时,请使用打包系统来执行此操作。这有很多优点。不仅维护您的应用程序变得非常简单,而且您还可以将部署过程搭载到环境中管理工具,如 Puppet、CfEngine 和 Marimba;只需将您的包上传到组织存储库,然后让这些工具安装正确版本的包——例如,就像您让它们安装正确版本的 Apache 一样。如果您需要在不同的盒子上安装不同的东西(也许您正在使用 n 层架构),您可以为每一层或每一类型的盒子创建一个包。打包二进制文件应该是部署管道的自动化部分。

Whenever your deployments involve sprinkling files across the filesystem or adding keys to the registry, use a packaging system to do it. This has many advantages. Not only does it become very simple to maintain your application, but you can also then piggyback your deployment process onto environment management tools like Puppet, CfEngine, and Marimba; just upload your packages to an organizational repository, and have these tools install the correct version of your package—the same way you’d have them install the right version of Apache, for example. If you need different things installed on different boxes (perhaps you’re using an n-tier architecture), you can create a package for each tier or type of box. Packaging your binaries should be an automated part of your deployment pipeline.

当然,并非所有部署都可以通过这种方式进行管理。例如,商业中间件服务器通常需要特殊工具来执行部署。在这种情况下,混合方法是必要的。使用包来获得不需要特殊工具的任何东西,然后使用专门的工具来执行剩余的部署。

Of course not all deployments can be managed in this way. Commercial middleware servers, for example, often require special tools to perform deployments. In this case, a hybrid approach is necessary. Use packages to get anything in place that doesn’t require special tools, and then use the specialized tools to perform the remainder of the deployment.


图片

您还可以使用特定于平台的打包系统,例如 Ruby Gems、Python Eggs、Perl 的 CPAN 等,来分发您的应用程序。但是,在创建用于部署的包时,我们倾向于更喜欢操作系统打包系统。如果您为该平台分发库,则特定于平台的工具可以很好地工作,但它们是由开发人员设计的,也是为开发人员设计的,而不是系统管理员。大多数系统管理员不喜欢这些工具,因为它们添加了另一层管理来处理,而这层管理并不总是与操作系统的包管理系统很好地配合。如果您要跨多个操作系统部署纯 Rails 应用程序,请务必使用 RubyGems 对其进行打包。不过,在可能的情况下,请坚持使用操作系统的标准包管理工具链。5个

You can also use platform-specific packaging systems, such as Ruby Gems, Python Eggs, Perl’s CPAN, and so on, to distribute your application. However, we tend to prefer the operating system packaging systems when creating packages for deployment. Platform-specific tools work fine if you’re distributing libraries for that platform, but they are designed by and for developers, not system administrators. The majority of system administrators dislike these tools because they add another layer of management to deal with, one that doesn’t always play nicely with the operating system’s package management system. If you are deploying a pure Rails application across multiple operating systems, by all means use RubyGems to package it. Where possible, though, stick to your operating system’s standard package management toolchain.5


确保部署过程是幂等的

Ensure the Deployment Process Is Idempotent

您的部署过程应该始终使目标环境处于相同(正确)的状态,而不管它在开始部署时发现它处于什么状态。

Your deployment process should always leave the target environment in the same (correct) state, regardless of the state it finds it in when starting a deployment.

实现这一目标的最简单方法是从已知良好的基准环境开始,自动或通过虚拟化进行配置。这个环境应该包括所有合适的中间件和你的应用程序工作所需的任何其他东西。然后,您的部署过程可以获取您指定的应用程序版本并将其部署到该环境,使用适用于您的中间件的部署工具。

The simplest way to achieve this is to start with a known-good baseline environment, provisioned either automatically or through virtualization. This environment should include all the appropriate middleware and anything else your application requires to work. Your deployment process can then fetch the version of the application you specify and deploy it to this environment, using the appropriate deployment tools for your middleware.

如果您的配置管理程序不足以实现这一目标,那么下一个最佳步骤是验证您的部署过程对环境所做的假设,如果不是,则部署失败遇见了。例如,您可以验证适当的中间件是否已安装、正在运行并且版本正确。在任何情况下,您还应该验证您的应用程序所依赖的任何服务是否正在运行并且版本正确。

If your configuration management procedures are not sufficiently good to achieve this, the next best step is to validate the assumptions your deployment process makes about the environment, and fail the deployment if they are not met. You could, for example, validate that the appropriate middleware is installed, running, and is at the correct version. You should, in any case, also validate that any services your application depends on are running and are at the correct version.

如果您的应用程序是作为一个整体进行测试、构建和集成的,那么将其作为一个整体进行部署通常是有意义的。这意味着每次部署时,您都应该根据从版本控制中的单个修订派生的二进制文件从头开始部署所有内容。这包括多层系统,例如,同时开发应用层和表示层。当您部署一层时,您应该部署所有层。

If your application is tested, built, and integrated as a single piece, it usually makes sense to deploy it as a single piece. This means that every time you deploy, you should deploy everything from scratch, based on binaries derived from a single revision in version control. This includes multiple-tier systems where, for example, the application and presentation tiers are developed at the same time. When you deploy one tier, you should deploy all tiers.

许多组织坚持认为您应该只部署那些已更改的工件,以尽量减少更改。但是,找出发生了什么变化的过程可能比从头开始部署更复杂、更容易出错。它也更难测试;当然,不可能测试这种过程的每一种可能排列,因此您没有考虑的病态情况将是在发布期间发生的情况,使您的系统处于未定义状态。

Many organizations insist that you should deploy only those artifacts that have changed, in order to minimize change. But the process of working out what has changed can be more complex and error-prone than just deploying from scratch. It is also much harder to test; it is, of course, impossible to test every possible permutation of such a process, so the pathological case you didn’t consider will be the one that happens during the release, leaving your system in an undefined state.

这条规则有一些例外。首先,在集群系统的情况下,同时重新部署整个集群并不总是有意义的;有关详细信息,请参阅第 263 页的“金丝雀发布”部分。

There are a few exceptions to this rule. Firstly, in the case of clustered systems, it doesn’t always make sense to redeploy the whole cluster simultaneously; see the “Canary Releasing” section on page 263 for more details.

其次,如果您的应用程序是组件化的,并且组件取自多个源代码存储库,您将需要部署从修订控制存储库中的修订元组(x、y、z、...)创建的二进制文件。在这种情况下,如果您知道只有一个组件发生了变化,并且您已经测试了将要在生产中使用的组件版本组合,那么您可以只部署发生变化的组件。这里的关键区别在于,从先前状态升级到新状态的过程已经过测试。相同的原则适用于形成面向服务的体系结构的各个服务。

Secondly, if your application is componentized and the components are taken from multiple source code repositories, you will need to deploy the binaries created from a tuple of revisions (x, y, z,...) from your revision control repositories. In this case, if you know that only one component has changed, and if you have already tested the combination of component versions you are about to have in production, then you can deploy only the component that is changing. The crucial distinction here is that the process of upgrading from the previous state to the new state has already been tested. The same principles apply to individual services that form a service-oriented architecture.

最后,另一种方法是使用本身具有幂等性的工具进行部署。例如,在低级别,Rsync 将确保一个系统上的目标目录与另一个系统上的源目录相同,无论目标目录中文件的状态如何,使用强大的算法仅通过网络传输目标目录和源目录的区别。执行目录更新的版本控制实现了类似的结果。Puppet,在第 11 章中有详细描述,“管理基础设施和环境”,分析目标环境的配置,并仅进行必要的更改以使其与所需环境状态的声明规范同步。BMC、HP 和 IBM 开发了大量的商业应用程序来管理部署和发布。

Finally, another approach is to use tools for deployment that are idempotent in their own right. At a low level, for example, Rsync will ensure that a target directory on one system is identical to the source directory on another system, whatever the state of the files in the target directory, using a powerful algorithm to transfer over the wire only the differences between the target directory and the source directory. Version control performing directory updates achieves a similar result. Puppet, described in detail in Chapter 11, “Managing Infrastructure and Environments,” analyzes the configuration of the target environment and makes only the necessary changes to bring it in sync with the declarative specification of the desired state of the environment. BMC, HP, and IBM produce a whole raft of commercial applications to manage deployments and releases.

逐步发展您的部署系统

Evolve Your Deployment System Incrementally

每个人都可以看到完全自动化部署过程的吸引力:“只需按一下按钮即可发布您的软件。” 当您看到以这种方式部署的大型企业系统时,它看起来很神奇。魔法的问题在于它从表面上看可能复杂得令人生畏。事实上,如果您检查我们的一个部署系统,它只是一系列非常简单的增量步骤,随着时间的推移,这些步骤会创建一个复杂的系统。

Everyone can see the appeal of a fully automated deployment process: “Release your software at the push of a button.” When you see a large enterprise system that is deployed this way, it looks like magic. The problem with magic is that it can look dauntingly complex from the outside. In fact, if you examine one of our deployment systems, it is merely a collection of very simple, incremental steps that—over time—create a sophisticated system.

我们的观点是,您不必完成所有步骤即可从您的工作中获得价值。第一次编写脚本将应用程序部署到本地开发环境并与团队共享时,您可以节省大量开发人员的工作量。

Our point here is that you don’t have to have completed all of the steps to get value from your work. The first time you write a script to deploy the application in a local development environment and share it with the team, you have saved lots of work of individual developers.

首先让运营团队与开发人员合作,将应用程序自动部署到测试环境中。确保操作人员对用于部署的工具感到满意。确保开发人员可以使用相同的流程在他们的开发环境中部署和运行应用程序。然后,继续完善这些脚本,以便在验收测试环境中使用它们来部署和运行应用程序,以便可以运行测试。然后,进一步向下移动部署管道并确保运营团队可以使用相同的工具将应用程序部署到暂存和生产中。

Start by getting the operations team to work with developers to automate deployment of the application into a testing environment. Make sure that the operations people are comfortable with the tools being used to deploy. Ensure that developers can use the same process to deploy and run the application in their development environments. Then, move on to refining these scripts so they can be used in the acceptance test environment to deploy and run the application so that the tests can be run. Then, move further down the deployment pipeline and ensure the operations team can use the same tools to deploy the application into staging and production.

面向 JVM 的应用程序的项目结构

Project Structure for Applications That Target the JVM

尽管本书旨在尽可能避免特定于技术,但我们觉得花一节来描述如何布局以 JVM 为目标的项目是值得的。这是因为,虽然有一些有用的约定,但它们并没有在 Maven 世界之外强制执行。6但是,如果开发人员遵循标准布局,他们的工作就会轻松很多。只需很少的额外努力,就可以将此处提供的信息抽象为其他技术。特别是,.NET 项目可以富有成效地使用完全相同的布局,当然用反斜杠代替正斜杠。7

Although this book aims to avoid being technology-specific as much as possible, we felt it worth spending a section to describe how to lay out projects that target the JVM. This is because, while there are useful conventions, they aren’t enforced outside the Maven world.6 However, it makes life much easier for developers if they follow the standard layout. It should be possible to abstract the information presented here to other technologies with little additional effort. In particular, .NET projects can fruitfully use the exact same layout, with backslashes substituted for forward slashes of course.7

项目布局

Project Layout

我们将介绍 Maven 假定的项目布局,称为 Maven 标准目录布局。即使您不使用(甚至不喜欢)Maven,它最重要的贡献之一就是引入了项目布局的标准约定。

We are going to present the project layout assumed by Maven, known as the Maven Standard Directory Layout. Even if you don’t use (or even like) Maven, one of its most important contributions is introducing a standard convention for project layout.

典型的源代码布局如下所示:

A typical source layout will look like this:

图片

如果您使用 Maven 子项目,它们每个都位于项目根目录中,子目录也遵循 Maven 标准目录布局。请注意,lib目录不是 Maven 的一部分——Maven 将自动下载依赖项并将它们存储在它管理的本地存储库中。但是,如果您不使用 Maven,那么将您的库作为源代码的一部分签入是有意义的。

If you use Maven subprojects, they each go in a directory at the project root, with subdirectories that also follow the Maven Standard Directory Layout. Note that the lib directory is not part of Maven—Maven will automatically download dependencies and store them in a local repository it manages. However, if you’re not using Maven, it makes sense to check your libraries in as part of your source code.

管理源代码

Managing Source Code

始终遵循标准的 Java 实践并将文件保存在以它们包含的包命名的目录中,每个文件一个类。Java 编译器和所有现代开发环境都将强制执行此约定,但我们仍然发现人们违反它的地方。如果您不遵循该语言的这个和其他约定,它可能会导致难以发现的错误,但更重要的是,它会使您的项目更难维护,并且编译器会发出警告。出于同样的原因,一定要遵循 Java 命名约定,包名以 PascalCase 命名,类以驼峰命名法命名。使用 CheckStyle 或 FindBugs 等开源工具在提交阶段的代码分析步骤中强制遵守这些命名约定。有关命名约定的更多信息,

Always follow the standard Java practice and keep your files in directories named after the package that they contain, with one class per file. The Java compiler and all modern development environments will enforce this convention, but we still find places where people violate it. If you don’t follow this and other conventions of the language, it can lead to hard-to-find bugs, but more importantly it makes it harder to maintain your project, and the compiler will issue warnings. For the same reasons, be sure to follow Java naming conventions, with package names in PascalCase and classes in camelCase. Use an open source tool such as CheckStyle or FindBugs to enforce adherence to these naming conventions in the code analysis step of your commit stage. For more on naming conventions, see Sun’s documentation “Code Conventions for the Java Programming Language” [asKdH6].

任何生成的配置或元数据(例如,由注释或 XDoclet 生成)不应在src目录中。相反,将它们放在目标目录中,以便在您运行干净的构建时可以删除它们,这样它们就不会被错误地签入版本控制。

Any generated configuration or metadata (e.g., generated by annotations or XDoclet) should not be in the src directory. Instead, put them in the target directory so they can be deleted when you run a clean build, and so they don’t get checked in to version control by mistake.

管理测试

Managing Tests

测试的所有源代码都进入目录test/[language]单元测试应该存储在代码包层次结构的镜像中——也就是说,给定类的测试应该与该类位于同一个包中。

All source code for tests goes into the directory test/[language]. Unit tests should be stored in a mirror of the package hierarchy of your code—that is, a test for a given class should be in the same package as that class.

其他类型的测试,例如验收测试、组件测试等,可以保存在单独的包集中,例如com.mycompany.myproject。acceptance.ui,com.mycompany.myproject.acceptance.api,com.mycompany。我的项目.集成但是,通常将它们保存在与其余测试相同的目录下。在您的构建脚本中,您可以使用基于包名称的过滤来确保它们被单独执行。有些人喜欢为不同类型的测试创建单独的测试目录——但这是一个偏好问题,因为 IDE 和构建工具完全能够处理这两种布局。

Other kinds of tests, such as acceptance tests, component tests, and so forth, can be kept in separate sets of packages, for example com.mycompany.myproject. acceptance.ui, com.mycompany.myproject.acceptance.api, com.mycompany. myproject.integration. However, it is usual to keep them under the same directory as the rest of your tests. In your build scripts, you can use filtering based on package name to ensure they get executed separately. Some people prefer to create separate directories under test for different kinds of tests—but this is a matter of preference since IDEs and build tools are quite capable of coping with both layouts.

管理构建输出

Managing Build Output

当 Maven 构建您的项目时,它会将所有内容放在项目根目录中名为target的目录中。这包括生成的代码、元数据文件(如 Hibernate 映射文件等)。将这些文件放入一个单独的目录中可以很容易地从以前的构建中清除工件,因为您所要做的就是删除该目录。您不应将此目录中的任何内容提交给版本控制;如果您决定签入任何二进制工件,请将它们复制到存储库中的另一个目录。源代码控制系统应忽略目标目录。Maven 在这个目录下创建文件如下:

When Maven builds your project, it puts everything in a directory at the project root called target. This includes generated code, metadata files such as Hibernate mapping files, etc. Putting these into a separate directory makes it easy to clean out artifacts from a previous build, since all you have to do is delete the directory. You should not commit anything from this directory to version control; if you do decide to check in any binary artifacts, copy them to another directory in the repository. The target directory should be ignored by your source control system. Maven creates files in this directory as follows:

图片

如果你不使用 Maven,你可以只使用target下一个名为reports的目录来存储测试报告。

If you’re not using Maven, you can just use a directory called reports under target to store test reports.

构建过程最终应该生成 JAR、WAR 和 EAR 形式的二进制文件。它们进入目标目录,由您的构建系统存储在您的工件存储库中。首先,每个项目都应该创建一个 JAR。但是,随着项目的增长,您可以为不同的组件创建不同的 JAR(有关组件的更多信息,请查看第 13 章“管理组件和依赖项”)。例如,您可以为代表整个组件或服务的系统的重要功能块创建 JAR。

The build process should ultimately generate binaries in the form of JARs, WARs, and EARs. These go into the target directory to be stored in your artifact repository by your build system. To start with, each project should create one JAR. However, as your project grows, you can create different JARs for different components (for more on components, check out Chapter 13, “Managing Components and Dependencies”). For example, you can create JARs for significant functional chunks of your system that represent whole components or services.

无论您采用何种策略,请记住创建多个 JAR 的目的有两个:首先,简化应用程序的部署,其次,您可以提高构建过程的效率并最大限度地减少依赖关系图的复杂性。这些注意事项应指导您如何打包应用程序。

Whatever your strategy, bear in mind that the point of creating multiple JARs is twofold: first, to make it simple to deploy your application, and second, so you can make your build process more efficient and minimize the complexity of your dependency graph. These considerations should guide you in how you package your application.

不是将所有代码存储为一个项目并创建多个 JAR,另一种方法是为每个组件或子项目创建单独的项目。一旦您的项目达到一定规模,从长远来看,这会更容易维护,尽管它也会妨碍某些 IDE 中代码库的可导航性。这种选择实际上取决于您的开发环境和不同组件中代码之间的耦合程度。在您的构建过程中创建一个单独的步骤来从构成它的各种 JAR 组装您的应用程序可以帮助保持灵活性以改变您对您所做的打包决定的想法。

Instead of storing all your code as one project and creating multiple JARs, an alternative is to create separate projects for each component or subproject. Once your project gets to a certain size, this can be easier to maintain in the long run, though it can also get in the way of navigability of the codebase in some IDEs. This choice really depends upon your development environment and the level of coupling between the code within different components. Creating a separate step in your build process to assemble your application from the various JARs that comprise it can help retain the flexibility to change your mind about the packaging decisions you make.

管理图书馆

Managing Libraries

您有多种管理库的选项。一种是将库管理完全委托给 Maven 或 Ivy 等工具。在这种情况下,您不需要将任何库签入版本控制——只需在您的项目规范中声明您需要的依赖项。另一方面,您可以将项目构建、测试或运行系统所需的所有库签入源代码管理,在这种情况下,通常将它们放入项目根目录下名为lib的目录中. 我们喜欢根据构建时、测试时或运行时是否需要这些库将它们分成不同的目录。

You have several options for managing libraries. One is to completely delegate library management to a tool like Maven or Ivy. In this case, you don’t need to check any libraries into version control—just declare the dependencies you require in your project specification. At the other end of the spectrum, you can check into source control all the libraries your project requires to build, test, or run the system, in which case it is common to put them into a directory called lib at the root of your project. We like to separate these libraries into different directories depending on whether they are required at build time, test time, or run time.

关于将构建时依赖项(例如 Ant 本身)存储到什么程度存在一些争论。我们认为这在很大程度上取决于项目的规模和持续时间。一方面,编译器或 Ant 版本等工具可能用于构建许多不同的项目,因此将它们存储在每个项目中会很浪费。不过,这里有一个权衡:随着项目的增长,维护依赖关系成为一个越来越大的问题。一个简单的解决方案是将大多数依赖项存储在您的版本控制系统中它自己的单独项目中。

There is some debate about how far to take the storage of build-time dependencies, such as Ant itself. We think that a lot depends on the size and duration of the project. On the one hand, tools like the compiler or version of Ant may be used to build many different projects, so storing them inside every project would be wasteful. There is a trade-off here, though: As a project grows, maintaining the dependencies becomes a bigger and bigger problem. One simple solution to this is to store most dependencies in a separate project of its own in your version control system.

一种更复杂的方法是在您的组织内创建一个存储库来存储您所有项目中所需的所有库。Ivy 和 Maven 都支持自定义存储库。在合规性很重要的组织中,这可以用作使适当祝福的库可用的一种方式。这些方法在第 13 章“管理组件和依赖性”中有更详细的讨论。

A more sophisticated approach is to create a repository within your organization to store all the libraries required in all your projects. Both Ivy and Maven support custom repositories. In organizations where compliance is important, this can be used as a way to make the appropriately blessed libraries available. These approaches are discussed in more detail in Chapter 13, “Managing Components and Dependencies.”

您需要确保您的应用程序所依赖的任何库都与您的应用程序的二进制文件一起作为部署管道的一部分打包,如第 154 页的“使用操作系统的打包工具”部分所述Ivy 和 Maven 在生产环境中没有位置。

You’ll need to ensure that any libraries your application depends on are packaged up along with your application’s binaries as part of your deployment pipeline, as described in the “Use Your Operating System’s Packaging Tools” section on page 154. Ivy and Maven have no place on production boxes.

部署脚本

Deployment Scripting

环境管理的核心原则之一是,对测试和生产环境的更改只能通过自动化流程进行。这意味着您不应远程登录此类系统来执行部署;他们应该完全按照脚本编写。执行方式有以下三种脚本部署。首先,如果您的系统将在单个盒子上运行,您可以编写一个脚本来完成需要在该盒子上本地完成的所有工作。

One of the core principles of environment management is that changes to testing and production environments should only be made through an automated process. That means that you should not log into such systems remotely to perform deployments; they should be entirely scripted. There are three ways to perform scripted deployments. First of all, if your system will run on a single box, you can write a script that will do everything that needs to be done locally on that box.

然而,大多数时候部署需要某种程度的编排——即在不同的计算机上运行脚本以执行部署。在这种情况下,您需要有一组部署脚本——一个用于部署过程的每个独立部分——并在所有必要的服务器上运行它们。这并不意味着每个服务器都会有一个脚本——例如,可能有一个脚本用于升级您的数据库,一个脚本用于将新的二进制文件部署到您的每个应用程序服务器,第三个脚本用于升级您的服务应用取决于。

However, most of the time deployment requires some level of orchestration—that is, running scripts on different computers in order to perform a deployment. In this case, you need to have a set of deployment scripts—one for each independent part of the deployment process—and run them on all the necessary servers. It doesn’t follow that there will be one script per server—for example, there might be one script to upgrade your database, one script to deploy a new binary to each of your application servers, and a third script to upgrade a service your application depends on.

您可以通过三种方式部署到远程机器上。第一种是编写一个脚本来登录每个框并运行适当的命令。第二种是编写一个在本地运行的脚本,并让代理在每台远程机器上运行该脚本。第三种选择是使用您平台的适当打包技术打包您的应用程序,并让基础设施管理或部署工具推出新版本,运行任何必要的工具来初始化您的中间件。第三个选项是最强大的,原因如下:

You have three options for deploying onto remote machines. The first is to write a script that logs into each box and runs the appropriate commands. The second is to write a script that runs locally, and have agents that run the script on each of the remote machines. The third option is to package your application up using your platform’s appropriate packaging technology and have an infrastructure management or deployment tool push out new versions, running any necessary tools to initialize your middleware. The third option is the most powerful, for the following reasons:

• ControlTier 和 BMC BladeLogic 等部署工具,以及 Marionette Collective、CfEngine 和 Puppet 等基础设施管理工具,都是声明式和幂等的,确保在所有必要的机器上安装正确版本的包,即使其中一些机器在运行时出现故障计划部署的时间,或者您是否将新机器或 VM 添加到您的环境中。有关这些工具的更多信息,请参阅第 11 章“管理基础架构和环境”。

• Deployment tools like ControlTier and BMC BladeLogic, and infrastructure management tools like Marionette Collective, CfEngine, and Puppet, are declarative and idempotent, ensuring that the right version of the packages is installed on all necessary boxes even if some of them are down at the time the deployment is scheduled, or if you add a new machine or VM to your environment. See Chapter 11, “Managing Infrastructure and Environments,” for more on these tools.

• 您可以使用同一套工具来管理应用程序部署和管理您的基础架构。由于是同一个人(运营团队)负责这两件事,而且两者是齐头并进的,因此使用一个工具来实现这两个目的是有意义的。

• You can use the same set of tools for both managing application deployment and managing your infrastructure. Since it’s the same people—the operations team—that are responsible for both of these things, and the two go hand-in-hand, it makes sense to use a single tool for both purposes.

如果此选项不可行,具有代理模型(也就是说,几乎所有代理模型)的持续集成服务器可以很容易地选择第二个选项。这种方法有几个好处:

If this option is not possible, continuous integration servers that have an agent model (that is to say, almost all of them) make it very easy to go with the second option. This approach has several benefits:

• 你必须做更少的工作:就像在本地执行一样编写脚本,将它们签入版本控制,然后让你的 CI 服务器在指定的远程机器上运行它们。

• You have to do less work: Just write the scripts as if they were being executed locally, check them into version control, and have your CI server run them on the specified remote machines.

• CI 服务器提供管理作业的所有基础设施,例如在出现故障时重新运行它们、显示控制台输出,以及提供一个仪表板,您可以在其中查看部署状态以及当前部署到的应用程序版本你的每一个环境。

• The CI server provides all the infrastructure for managing jobs, such as rerunning them in the event of a failure, showing console output, and providing a dashboard where you can see the status of your deployments and which versions of your application are currently deployed to each of your environments.

• 根据您的安全要求,让您盒子上的 CI 代理调用 CI 服务器以获得他们需要的一切可能是有意义的,而不允许脚本远程访问测试和生产环境。

• Depending on your security requirements, it may make sense to have the CI agents on your boxes call into the CI server to get everything they need, without allowing scripts to access testing and production envionments remotely.

最后,如果出于某种原因您不能使用上述任何工具,您可以编写自己的部署脚本。如果您的远程机器是 UNIX,您可以使用普通的旧 Scp 或 Rsync 来复制二进制文件和数据,并使用 Ssh 来执行相关命令来执行部署。如果您使用的是 Windows,也有适合您的选项:PsExec 和 PowerShell。还有更高级别的工具,例如 Fabric、Func 和 Capistrano,它们可以为您处理具体细节,让编写您自己的部署脚本变得非常简单。

Finally, if there is some reason you can’t use any of the tools described above, you can script your own deployments. If your remote machines are UNIX, you can use plain old Scp or Rsync to copy over binaries and data, and Ssh to execute the relevant commands to perform deployments. If you’re using Windows, there are options for you too: PsExec and PowerShell. There are also higher-level tools such as Fabric, Func, and Capistrano that take care of the nuts and bolts for you, making scripting your own depoyments pretty straightforward.

但是,无论是使用 CI 系统还是编写自己的部署脚本都不会处理错误情况,例如部分完成的部署,或者新节点添加到网格并需要配置和部署到的情况。因此,最好使用适当的部署工具。

However, neither using your CI system nor scripting your own deployments will deal with error conditions, such as partially completed deployments, or with the case where a new node is added to the grid and needs to be provisioned and deployed to. For this reason, using a proper deployment tool is preferable.

该领域可用的工具在不断发展。在本书的网站 [dzMeNE] 上有使用其中一些工具的示例,以及更新工具的更新。

The tools available in this field are continuously evolving. There are examples of using some of these tools, and updates on newer tools as they come out, at this book’s website [dzMeNE].

部署和测试层

Deploying and Testing Layers

如果我们的一般交付方法以及具体的复杂系统的构建和部署方法有一个基本核心,那就是您应该始终努力在已知良好的基础上构建。我们不会费心测试不编译的更改,我们不会费心尝试验收测试未通过提交测试的更改,等等。

If there is a fundamental core to our approach to delivery in general and to the building and deployment of complex systems specifically, it is that you should always strive to build on foundations that are known to be good. We don’t bother testing changes that don’t compile, we don’t bother trying to acceptance-test changes that have failed commit tests, and so on.

图 6.2 分层部署软件

Figure 6.2 Deploying software in layers

图片

当需要将我们的发布候选部署到类似生产的环境中时,情况更是如此。在我们甚至懒得将二进制可交付成果复制到文件系统中的正确位置之前,我们想知道我们的环境已经为我们准备好了。为实现这一点,我们喜欢将部署视为放置一系列层,如图 6.2所示。

This is even more true when the time comes to deploy our release candidates into production-like environments. Before we even bother to copy our binary deliverables to the correct place in the filesystem, we want to know that our environment is ready for us. To achieve this, we like to think of deployment as depositing a series of layers, as shown in Figure 6.2.

最底层是操作系统。接下来是中间件和您的应用程序所依赖的任何其他软件。一旦这两个层就位,它们将需要应用一些特定的配置来为我们的应用程序的部署做好准备。只有在添加了它之后,我们才能部署我们的软件——可部署的二进制文件、任何服务或守护进程,以及它们自己的相关配置。

The lowest layer is the operating system. Next is the middleware and any other software your application depends on. Once both these layers are in place, they will need some specific configuration applied to prepare them for the deployment of our application. Only after this has been added, can we deploy our software—the deployable binaries, any services or daemons, and their own associated configuration.

测试您的环境配置

Testing Your Environment’s Configuration

如果部署不正确,您部署的每一层都可能会阻止应用程序正常运行。这意味着您应该在应用每个层时对其进行测试,以便在出现问题时可以快速使环境配置过程失败。测试应该清楚地指出问题所在。

Each layer that you deploy may, if deployed incorrectly, prevent the application from functioning as it should. This means that you should test each layer as it is applied, so that you can fail the environment configuration process quickly if a problem occurs. The test should give a clear indication of where the problem lies.

这些测试不需要详尽无遗。他们只需要捕获常见故障或代价高昂的潜在故障。它们应该是非常简单的“冒烟测试”,断言关键资源是否存在。目标是提供一定程度的信心,即刚刚部署的层正在工作。

These tests need not be exhaustive. They only need to catch common failures or costly potential failures. They should be very simple “smoke tests” that assert the presence or absence of key resources. The objective is to provide a degree of confidence that the layer that has just been deployed is working.

图 6.3 部署测试层

Figure 6.3 Deployment testing layers

图片

您编写的基础设施冒烟测试对于任何给定系统都是独一无二的。但测试的目的是一致的:证明环境的配置符合我们的预期。在第 317 页的“监视基础结构和应用程序”部分中有更多关于基础结构监视的内容为了让您了解我们的想法,以下是我们过去发现有用的一些测试示例:

The infrastructure smoke tests that you write will be unique for any given system. But the intention of the tests is consistent: to prove that the environment’s configuration matches our expectations. There is more on infrastructure monitoring in the “Monitoring Infrastructure and Applications” section on page 317. To give you a sense of what we have in mind, here are some examples of tests that we found useful in the past:

• 确认我们可以从我们的数据库中检索记录。

• Confirm that we can retrieve a record from our database.

• 确认我们可以联系该网站。

• Confirm that we can contact the website.

• 断言我们的消息代理在其中注册了正确的消息集。

• Assert that our message broker has the correct set of messages registered in it.

• 通过我们的防火墙发送几个“ping”以证明它允许我们的流量并在我们的服务器之间提供循环负载分配。

• Send several “pings” through our firewall to prove that it allows our traffic and provides a round-robin load distribution between our servers.

技巧和窍门

Tips and Tricks

在本节中,我们将列出一些用于解决常见构建和部署问题的解决方案和策略。

In this section we will list some solutions and strategies that we have used to solve common build and deployment problems.

始终使用相对路径

Always Use Relative Paths

构建中最常见的错误是默认使用绝对路径。这在特定机器的配置和您的构建过程之间产生了紧密的依赖性,使得配置和维护其他服务器变得困难。例如,它使得不可能在一台机器上对同一个项目进行两次签出——这种做法在很多不同的情况下都非常有用,从比较调试到并行测试。

The most common mistake in a build is to use absolute paths by default. This creates a tight dependency between the configuration of a specific machine and your build process, making it hard to configure and maintain other servers. For example, it makes it impossible to have two check-outs of the same project on one machine—a practice that can be very helpful in lots of different situations, from comparative debugging to parallel testing.

您的默认设置应该是对所有内容使用相对路径。这样,您构建的每个实例都是完全独立的,并且您提交给版本控制系统的图像会自动确保一切都在正确的位置并按预期工作。

Your default should be to use relative paths for everything. That way, each instance of your build is wholly self-contained and the image that you commit to your version control system automatically ensures that everything is in the right place and works as it should.

有时,很难避免使用绝对路径。尝试发挥创意并尽可能避免它。如果您被迫使用绝对路径,请确保它们是您构建中的特殊情况,而不是正常方法。确保它们保存在属性文件或独立于您的构建系统的其他一些配置机制中。可能有几个充分的理由需要使用绝对路径。第一个是如果您必须与依赖硬编码路径的第三方库集成。尽可能隔离系统的这些部分,不要让它们感染构建的其余部分。

Occasionally, the use of absolute paths is hard to avoid. Try to be creative and avoid it where possible. If you are forced to use absolute paths, make sure they are the special case in your build, not the normal approach. Ensure they are kept in properties files or some other configuration mechanism that is independent of your build system. There may be a couple of good reasons for the use of absolute paths to be necessary. The first is if you have to integrate with third-party libraries that rely on hard-coded paths. Isolate these parts of your system as much as possible and don’t let them infect the rest of your build.

即使在部署应用程序时,也可以避免使用绝对路径。每个操作系统和应用程序堆栈都有安装软件的约定,例如 UNIX 的文件系统层次结构标准 (FHS)。使用系统的打包工具来强制执行这些约定。如果你必须安装在一些非标准的地方,通过你的配置系统中的一个选项来完成。尝试通过使系统中的所有路径都与一个或多个定义明确的根路径(部署根、配置根等)相关并仅覆盖这些根来尽量减少这些路径。

Even when deploying your application, it is possible to avoid absolute paths. Every operating system and application stack has a convention for installing software, such as UNIX’s Filesystem Hierarchy Standard (FHS). Use your system’s packaging tools to enforce these conventions. If you must install in some nonstandard place, do this through an option in your configuration system. Try to minimize these by making all of the paths in your system relative to one or more well-defined root paths—the deployment root, the configuration root, and so on—and overriding just these roots.

有关在部署时配置应用程序的更多信息,请查看第 2 章“配置管理”。

For more information on configuring your application at deployment time, take a look at Chapter 2, “Configuration Management.”

消除手动步骤

Eliminate Manual Steps

令人惊讶的是,有多少人手动或通过 GUI 驱动的工具部署他们的软件。对于许多组织而言,“构建脚本”是一个包含一系列说明的打印文档,例如:

It is amazing how many people deploy their software manually or though GUI-driven tools. For many organizations, a “build script” is a printed document with a series of instructions like:

图片

这种类型的部署既乏味又容易出错。文档总是错误的或过时的,因此需要在预生产环境中进行大量排练。每个部署都是独一无二的——一个错误修复或对系统的一个小改动可能只需要重新部署系统的一个或两个部分。因此,必须为每个版本修改部署过程。以前部署的知识和工件不能重复使用。每个部署实际上都是对执行它的个人的记忆和对系统的理解的练习,并且从根本上说它很容易出错。

This type of deployment is tedious and error-prone. The documentation is always wrong or out-of-date, and therefore requires extensive rehearsal in preproduction environments. Every deployment is unique—a bugfix or a small change to the system may require only one or two parts of the system to be redeployed. So, the deployment procedure must be revised for each release. Knowledge and artifacts from previous deployments cannot be reused. Each deployment is really an exercise of memory and understanding of the system for the individual performing it, and it is fundamentally error-prone.

那么,您应该何时考虑自动化流程?最简单的答案是,“当你必须做第二次的时候。” 第三次做某事时,应该使用自动化流程来完成。这种细粒度的增量方法可以快速创建一个系统,用于自动执行开发、构建、测试和部署过程的重复部分。

So, when should you think about automating a process? The simplest answer is, “When you have to do it a second time.” The third time you do something, it should be done using an automated process. This fine-grained incremental approach rapidly creates a system for automating the repeated parts of your development, build, test, and deployment process.

内置从二进制文件到版本控制的可追溯性

Build In Traceability from Binaries to Version Control

能够从任何给定的二进制文件中确定使用版本控制中的哪个修订来生成它是至关重要的。如果您在生产环境中遇到问题,能够准确地弄清楚每个组件在那个盒子上的版本以及它们来自哪里可以挽救生命(Bob Aiello 在他的书Configuration Management Best中讲述了一个关于这个的好故事做法)。

It’s essential to be able to determine from any given binary which revision in version control was used to generate it. If you have a problem in your production environment, being able to figure out exactly which versions of each component are on that box and where they came from can be a life saver (Bob Aiello tells a great story about this in his book Configuration Management Best Practices).

有多种方法可以做到这一点。在 .NET 中,您可以在程序集中包含版本控制元数据——确保您的构建脚本始终执行此操作,并包含版本控制修订标识符。JAR 文件还可以在其清单中包含元数据,因此您可以在此处执行类似的操作。如果您使用的技术不支持将元数据构建到您的包中,您可以采用另一种方式:获取构建过程生成的每个二进制文件的 MD5 散列及其名称和它来自的修订标识符,并将它们存储在一个数据库。这样你就可以获取任何二进制文件的 MD5 并准确确定它是什么以及它来自哪里。

There are various ways to do this. In .NET, you can include versioning metadata in assemblies—make sure your build scripts always do this, and include the version control revision identifier. JAR files can also include metadata in their manifests, so you can do something analogous here. If the technology you use doesn’t support building metadata into your packages, you can go the other way: Take an MD5 hash of each binary your build process generates along with its name and the revision identifier it came from, and store them in a database. That way you can take the MD5 of any binary and determine exactly what it is and where it came from.

不要将二进制文件作为构建的一部分检入版本控制

Don’t Check Binaries into Version Control as Part of Your Build

作为构建过程的一部分,将二进制文件或报告检查到版本控制中有时似乎是个好主意。但是,一般来说,您应该抵制这一点。从几个方面来说,这是一个坏主意。

It can sometimes seem like a good idea to check binaries or reports into version control as part of your build process. However, in general, you should resist this. It is a bad idea in several ways.

首先,修订控制标识符最重要的功能之一是能够跟踪特定签入集发生了什么。通常,您会将版本控制 ID 与构建标签相关联,并使用它来跟踪一组更改,这些更改通过不同的环境进入生产环境。如果您从构建中签入二进制文件和报告,这意味着与版本控制修订标识符对应的二进制文件将具有自己的不同修订标识符——这是造成混淆的一个原因。

First, one of the most important functions of revision control identifiers is to be able to trace what happened to a particular set of check-ins. Usually you will associate a revision control ID with a build label and use that to trace a set of changes through the different environments it passes through into production. If you check in binaries and reports from your build, that means the binaries corresponding to a version control revision identifier will have a different revision identifier of their own—a recipe for confusion.

相反,将二进制文件和报告放在共享文件系统上。如果您丢失了它们或需要重新创建它们,最好的做法是获取源代码并重新创建它们。如果您无法可靠地从您的源代码重新创建二进制文件,则意味着您的配置管理没有达到标准,需要改进。

Instead, put binaries and reports onto a shared filesystem. If you lose them or need to re-create them, the best practice is to get the source and create them again. If you are unable to reliably re-create binaries from your source, it means your configuration management isn’t up to scratch and needs to be improved.

一般的经验法则是不要将作为构建、测试和部署周期的一部分创建的任何内容签入到源代码管理中。相反,将这些工件视为与触发构建的修订标识符相关联的元数据。大多数现代 CI 和发布管理服务器都有工件存储库和元数据管理系统,可以帮助您做到这一点,或者您可以使用 Maven、Ivy 或 Nexus 等工具。

The general rule of thumb is not to check in anything created as part of your build, test, and deploy cycle into source control. Instead, treat these artifacts as metadata to be associated with the identifier of the revision that triggered the build. Most modern CI and release management servers have artifact repositories and metadata management systems that can help you do this, or you can use tools like Maven, Ivy, or Nexus.

测试目标不应使构建失败

Test Targets Should Not Fail the Build

在某些构建系统中,默认行为是当任务失败时构建立即失败。例如,如果您有一个“测试”目标,并且该目标中指定的测试失败,那么整个构建将在目标运行后立即失败。这几乎总是一件坏事——相反,记录活动失败的事实,并继续构建过程的其余部分。然后,在流程结束时,查看是否有任何单个任务失败,如果失败,则退出并返回失败代码。

In some build systems, the default behavior is that the build fails immediately when a task fails. If you have a “test” target, for example, and the tests specified in that target fail, then the whole build will fail immediately after the target is run. This is almost always a Bad Thing—instead, record the fact that the activity has failed, and continue with the rest of the build process. Then, at the end of the process, see if any of the individual tasks failed and, if so, exit with a failure code.

出现这个问题是因为在许多项目中,有多个测试目标是有意义的。例如,在一个提交测试套件中,可能有一组单元测试、几个集成测试和少量验收冒烟测试。如果单元测试首先运行但构建失败,那么我们将不知道集成测试是否会通过,直到我们下次签入。更多的时间被浪费了。

This problem arises because in many projects, it makes sense to have multiple test targets. For example, in a commit test suite there may be a set of unit tests, a couple of integration tests, and a smattering of acceptance smoke tests. If the unit tests run first and fail the build, then we won’t know if the integration tests were going to pass until the next time we check in. More wasted time.

更好的做法是在生成更有用的报告或运行一组更完整的测试后,让失败设置一个标志,但稍后构建失败。例如,在 NAnt 和 Ant 中,这可以通过在测试任务上使用失败属性来完成。

A better practice is to make the failure set a flag but fail the build later, after generating more useful reports or running a more complete set of tests. For example, in NAnt and Ant this can be done using a failure-property attribute on the test task.

通过集成冒烟测试约束您的应用程序

Constrain Your Application with Integrated Smoke Tests

交互设计人员通常会限制界面以防止不需要的用户输入。同样,您可以限制您的应用程序,使它们在发现自己处于不熟悉的情况下时无法工作。例如,您可以让部署脚本在部署任何内容之前检查它们是否在正确的机器上运行。这对于测试和生产配置尤为重要。

Interaction designers often constrain interfaces to prevent undesirable user input. In the same way, you can constrain your applications so that they do not work if they find themselves in an unfamiliar situation. For example, you can make deployment scripts check that they are running on the correct machine before they deploy anything. This is particularly important for testing and production configurations.

几乎所有系统都有一个周期性运行的“批处理”部分。在会计系统中,有些组件每月、每季度或每年只运行一次。在这种情况下,请确保部署的版本在安装时验证其配置。您不想在明年 1 月 1 日凌晨 3 点调试您今天执行的安装。

Almost all systems have a “batch processing” piece that runs periodically. In accounting systems, there are components that only run once a month, once a quarter, or once a year. In this case, make sure the deployed version validates its configuration when it is installed. You don’t want to be debugging the install you did today at 3 A.M. on January 1st of the next year.

.NET 提示和技巧

.NET Tips and Tricks

.NET 有其自身的特点——这里有一些您应该注意的事项。

.NET has its own peculiarities—here are a few things you should watch out for.

.NET 中的解决方案和项目文件包含对它们实际构建的文件的引用。如果一个文件没有被引用,它就不会被构建。这意味着文件有可能从解决方案中删除但仍然存在于文件系统中。这可能会导致难以诊断的问题,因为在某个地方,有人会查看此文件并想知道它的用途。通过删除这些文件来保持项目清洁很重要。一种简单的方法是在所有解决方案中打开“显示隐藏文件”功能,然后留意没有图标的文件。当您看到一个时,将其从您的源代码控制系统中删除。

Solution and project files in .NET contain references to the files they will actually build. If a file is not referenced, it won’t get built. This means that it is possible for a file to be removed from the solution but still exist on the filesystem. This can lead to hard-to-diagnose problems, since somewhere, someone will look at this file and wonder what it is for. It is important to keep your project clean by removing these files. One simple way to do this is to turn on the Show Hidden Files feature in all your solutions, and then keep an eye out for files with no icon. When you see one, delete it from your source control system.

理想情况下,当您从解决方案中删除它们时,这会自动发生,但不幸的是,大多数与 Visual Studio 集成的源代码管理集成工具不会执行此操作。在等待工具供应商实现这一点的同时,重要的是要关注这个问题。

Ideally, this would happen automatically when you delete them from the solution, but unfortunately most source control integration tools that integrate with Visual Studio do not do this. While waiting for the tool vendors to implement this, it is important to keep an eye on this problem.

注意 bin 和 obj 目录。确保您的清理删除了解决方案中的所有 bin 和 obj 目录。确保这一点的一种方法是让您的“干净”调用 Devenv 的干净解决方案命令。

Watch out for bin and obj directories. Make sure your clean deletes all the bin and obj directories in your solution. One way to ensure this is to have your “clean” call Devenv’s clean solution command.

概括

Summary

我们在相当广泛的意义上使用术语“脚本”。一般来说,我们指的是帮助我们构建、测试、部署和发布软件的所有自动化。当您从部署管道的末端处理大量脚本时,它看起来非常复杂。然而,构建或部署脚本中的每项任务都很简单,过程本身并不复杂。我们非常强烈的建议是使用构建和部署过程作为脚本集合的指南。逐步提高您的自动化构建和部署能力,通过迭代识别然后自动化最痛苦的步骤来完成部署管道。始终牢记最终目标——即在开发、测试和生产之间共享相同部署机制的目标,但不要在创建工具的早期就太执着于这个想法。但是,请让操作人员和开发人员都参与创建这些机制。

We use the term “script” in quite a broad sense. Generally, by this we mean all the automation that helps us build, test, deploy, and release our software. When you approach that broad collection of scripts from the end of the deployment pipeline, it looks dauntingly complex. However, each task in a build or deployment script is simple, and the process itself is not a complex one. Our very strong advice is to use the build and deployment process as a guide to your collection of scripts. Grow your automated build and deployment capabilities step by step, working through the deployment pipeline by iteratively identifying and then automating the most painful steps. Keep the end goal in mind all the time—that is, the goal of sharing the same deployment mechanism between development, testing, and production, but don’t get too hung up on that thought early in the creation of your tools. Do, however, involve both operations and developers in the creation of these mechanisms.

如今,存在用于编写构建、测试和部署过程脚本的多种技术。即使是 Windows,传统上在自动化方面关系不大,随着 PowerShell 的到来以及 IIS 和 Microsoft 堆栈的其余部分中的脚本接口,也有一些令人羡慕的工具可供使用。我们在本章中突出显示了最受欢迎的资源,并提供了有关这些资源和其他资源的更多信息的指针。显然,在这样一本普通的书中,我们只能粗浅地了解这个主题。如果我们让您对构建脚本的基础以及向您开放的各种可能性有了扎实的了解——更重要的是,启发了您继续前进并实现自动化——那么我们就实现了我们的目标。

These days, a wide variety of technologies exist for scripting your build, test, and deployment process. Even Windows, traditionally the poor relation when it comes to automation, has some enviable tools at its disposal with the arrival of PowerShell and the scripting interfaces in IIS and the rest of the Microsoft stack. We have highlighted the most popular ones in this chapter and provided pointers to further information on these and other resources. Obviously, we can’t do more than scratch the surface of this topic in a general book like this. If we have given you a solid understanding of the foundations of build scripting and the various possibilities open to you—and more importantly, inspired you to go forth and automate—then we have achieved our goal.

最后,值得重申的是,脚本是您系统的首要部分。他们应该为它的整个生命而活。它们应该是版本控制的、维护的、测试的和重构的,并且是唯一的您用来部署软件的机制。如此多的团队将他们的构建系统视为事后的想法;根据我们的经验,在设计方面,构建和部署系统几乎总是关系不佳。因此,这种维护不善的系统往往是明智的、可重复的发布过程的障碍,而不是它的基础。交付团队应该花时间和精力来正确构建和部署脚本。对于您团队中的实习生来说,这不是一项艰巨的任务。花一些时间,想想你想要实现的目标,并设计你的构建和部署过程来实现这些目标。

Finally, it bears reiterating that scripts are first-class parts of your system. They should live for its entire life. They should be version-controlled, maintained, tested, and refactored, and be the only mechanism that you use to deploy your software. So many teams treat their build system as an afterthought; in our experience, build and deployment systems are nearly always the poor relation when it comes to design. As a result, such poorly maintained systems are often the barrier to a sensible, repeatable release process, rather than its foundation. Delivery teams should spend time and care to get the build and deployment scripts right. This is not a task for an intern on your team to cut his or her teeth on. Spend some time, think about the goals you want to achieve, and design your build and deployment process to attain them.

第 7 章提交阶段

Chapter 7. The Commit Stage

介绍

Introduction

提交阶段从对项目状态的更改开始,即向版本控制系统提交。它以失败报告结束,如果成功,则以二进制工件和可部署程序集的集合结束,以用于后续测试和发布阶段,以及关于应用程序状态的报告。理想情况下,提交阶段的运行时间应该少于五分钟,当然不超过十分钟。

The commit stage begins with a change to the state of the project—that is, a commit to the version control system. It ends with either a report of failure or, if successful, a collection of binary artifacts and deployable assemblies to be used in subsequent test and release stages, as well as reports on the state of the application. Ideally, a commit stage should take less than five minutes to run, and certainly no more than ten.

提交阶段以多种方式代表部署管道的入口。它不仅是创建新发布候选的时间点;这也是许多团队开始实施部署管道时的起点。当一个团队实施持续集成的实践时,它会在这样做的过程中创建一个提交阶段。

The commit stage represents, in more ways than one, the entrance into the deployment pipeline. Not only is it the point at which a new release candidate is created; it is also where many teams start when they begin to implement a deployment pipeline. When a team implements the practice of continuous integration, it creates a commit stage in the process of doing so.

这是至关重要的第一步。使用提交阶段可确保您的项目将花费在代码级集成上的时间降至最低。它推动了良好的设计实践,并对代码质量和交付速度产生了巨大影响。

It is the vital first step. Using a commit stage ensures that your project will minimize the time spent on code-level integration. It drives good design practices and has a dramatic effect on the quality of code—and the speed of delivery too.

提交阶段也是您应该开始构建部署管道的阶段。

The commit stage is also the point at which you should begin the construction of your deployment pipeline.

图 7.1 提交阶段

Figure 7.1 The commit stage

图片

我们已经在前面的章节“持续集成”和“部署管道剖析”中简要描述了提交阶段。在本章中,我们通过更详细地描述如何创建有效的提交阶段和有效的提交测试来扩展该材料。这主要是开发人员感兴趣的,他们是提交阶段反馈的主要消费者。提交阶段如图 7.1所示。

We have already briefly described the commit stage in earlier chapters, “Continuous Integration” and “Anatomy of the Deployment Pipeline.” In this chapter, we expand upon that material by describing in more detail how to create an effective commit stage and efficient commit tests. This will primarily be of interest to developers, who are the main consumers of feedback from the commit stage. The commit stage is shown in Figure 7.1.

为了刷新您的记忆,提交阶段的工作方式如下。有人在版本控制中检查对主线(主干)的更改。您的持续集成服务器会检测更改、检查源代码并执行一系列任务,包括

To refresh your memory, the commit stage works as follows. Somebody checks a change into mainline (trunk) in version control. Your continuous integration server detects the change, checks out the source code, and performs a series of tasks, including

• 编译(如有必要)并针对集成源代码运行提交测试

• Compiling (if necessary) and running the commit tests against the integrated source code

• 创建可以部署到任何环境中的二进制文件(如果您使用的是编译语言,这将包括编译和汇编)

• Creating binaries that can be deployed into any environment (this will include compiling and assembling if you’re using a compiled language)

• 执行任何必要的分析以检查代码库的健康状况

• Performing any analysis necessary to check the health of the codebase

• 创建稍后将在部署管道中使用的任何其他工件(例如数据库迁移或测试数据)

• Creating any other artifacts (such as database migrations or test data) that will be used later in the deployment pipeline

这些任务由持续集成服务器运行的构建脚本编排。您可以在第 6 章“构建和部署脚本”中阅读有关构建脚本的更多信息。然后将二进制文件(如果该阶段成功)和报告存储到您的中央工件存储库中,供您的交付团队和管道中的后续阶段使用。

These tasks are orchestrated by build scripts that are run by your continuous integration server. You can read more about build scripting in Chapter 6, “Build and Deployment Scripting.” The binaries (if the stage succeeds) and reports are then stored into your central artifact repository for use by your delivery team and by later stages in the pipeline.

对于开发者来说,提交阶段是开发过程中最重要的反馈周期。它针对作为开发人员引入系统的最常见错误提供快速反馈。提交阶段的结果代表了每个发布候选者生命中的重要事件。在此阶段取得成功是进入部署管道并启动软件交付流程的唯一途径。

For developers, the commit stage is the most important feedback cycle in the development process. It provides rapid feedback on the most common errors that they, as developers, introduce to the system. The result of the commit stage represents a significant event in the life of every release candidate. Success at this stage is the only way to enter the deployment pipeline and thus initiate the software delivery process.

提交阶段的原则和实践

Commit Stage Principles and Practices

如果部署管道的目标之一是消除不适合投入生产的构建,那么提交阶段就是门口的保镖。其目的是确保在任何不受欢迎的人造成任何麻烦之前将其拒绝。提交阶段的主要目标是创建可部署的工件,或者快速失败并通知团队失败的原因。

If one of the goals of the deployment pipeline is to eliminate builds that are not fit to make it into production, then the commit stage is the bouncer at the door. Its aim is to ensure that any undesirables are rejected before they cause any trouble. The principal goal of the commit stage is to either create deployable artifacts, or fail fast and notify the team of the reason for the failure.

以下是一些有助于实现有效提交阶段的原则和实践。

Here are some principles and practices that make for an effective commit stage.

提供快速、有用的反馈

Provide Fast, Useful Feedback

提交测试中的失败通常可归因于以下三个原因之一。要么在代码中引入了语法错误,被编译语言编译捕获;要么 或应用程序中引入了语义错误,导致一项或多项测试失败;或者应用程序或其环境(包括操作系统)的配置有问题。不管是什么问题,一旦失败,提交阶段应该在提交测试完成后立即通知开发人员,并提供失败原因的简明摘要,例如失败测试列表、编译错误或任何其他错误情况。开发人员还应该能够轻松地从提交阶段的运行中访问控制台输出,这些输出可能会分成几个框。

Failures in the commit tests can usually be attributed to one of the following three causes. Either a syntax error has been introduced into the code, caught by compilation in compiled languages; or a semantic error has been introduced into the application, causing one or more tests to fail; or there is a problem with the configuration of the application or its environment (including the operating system). Whatever the problem, in case of failures, the commit stage should notify the developers as soon as the commit tests are complete and provide a concise summary of the reasons for the failures, such as a list of failed tests, the compile errors, or any other error conditions. The developers should also be able to easily access the console output from the running of the commit stage, which may be split across several boxes.

如果及早发现错误,接近它们引入的位置,那么错误最容易修复。这不仅是因为将它们引入系统的人对它们记忆犹新,还因为发现错误原因的机制更简单。如果开发人员进行了导致测试失败的更改并且失败的原因不是很明显,那么自然要做的是查看自上次系统工作以来发生的所有更改以缩小测试的重点搜索。

Errors are easiest to fix if they are detected early, close to the point where they were introduced. This is not only because they are fresh in the minds of those who introduced them to the system, but also because the mechanics of discovering the cause of the error are simpler. If a developer makes a change that results in a failing test and the cause of the failure is not immediately obvious, the natural thing to do is to look at everything that has changed since the last time the system was working to narrow the focus of the search.

如果该开发人员一直听从我们的建议并经常进行更改,那么每次更改的范围都会很小。如果部署管道能够快速识别故障,最好是在提交阶段,那么更改的范围仅限于开发人员亲自进行的更改。这意味着修复在提交阶段发现的问题比在流程后期发现的问题要简单得多,在这些阶段可能会测试大量批处理的更改。

If that developer has been following our advice and committing changes frequently, the scope of each change will be small. If the deployment pipeline is able to identify that failure quickly, ideally at the commit stage, then the scope of the change is limited to those changes made personally by the developer. This means that fixing the problems found in the commit stage is significantly simpler than those identified later in the process, in stages that may be testing a larger number of changes batched together.

因此,为了使我们的部署管道高效,我们需要尽早捕获错误。在大多数项目中,我们实际上通过最大限度地使用现代开发环境,甚至在提交阶段之前就开始了这个过程——努力修复任何编译时警告(如果适用)或语法错误,一旦它们在我们的开发环境中突出显示。许多现代 CI 服务器还提供称为预测试提交预检构建的功能,该功能在签入更改之前针对更改运行提交阶段。如果您没有此功能,则必须在提交之前在本地编译并运行提交测试.

So, for our deployment pipeline to be efficient, we need to catch errors as early as possible. On most projects we actually begin this process even before the commit stage by maximizing our use of modern development environments—working hard to fix any compile-time warnings (if applicable) or syntax errors as soon as they are highlighted in our development environments. Many modern CI servers also provide the function called pretested commit, or preflight build, that runs the commit stage against the changes before they are checked in. If you don’t have this facility, you must compile and run your commit tests locally before committing.

提交阶段是第一个正式步骤,它使我们对质量的关注超出了单个开发人员的范围。提交阶段发生的第一件事是提交者的更改与主线集成,然后执行集成应用程序的一种自动化“校对”。如果我们要坚持及早识别错误的目标,我们需要专注于快速失败,因此我们需要提交阶段来捕获开发人员可能引入应用程序的大部分错误。

The commit stage is the first formal step that takes our focus on quality beyond the scope of an individual developer. The first thing that happens in the commit stage is that the committer’s changes are integrated with the mainline, and then a kind of automated “proofreading” of the integrated application is performed. If we are to stick to our aim of identifying errors early, we need to focus on failing fast, so we need the commit stage to catch most of the errors that developers are likely to introduce into the application.

在采用持续集成的早期,一个常见的错误是过于字面地理解“快速失败”的原则,并在发现错误时立即使构建失败。这几乎是正确的,但优化得太过分了。我们通常将提交阶段划分为一系列任务(具体任务取决于项目),例如编译、运行单元测试等。我们只会在错误阻止阶段的其余部分运行时停止提交阶段——例如,编译错误。否则,我们将提交阶段运行到最后并提供所有错误和失败的汇总报告,以便立即修复它们。

A common error early in the adoption of continuous integration is to take the doctrine of “fail fast” a little too literally and fail the build immediately when an error is found. This is nearly right, but is optimized too far. We generally divide our commit stage into a series of tasks (the exact tasks will depend on the project) such as compiling, running unit tests, and so forth. We only stop the commit stage if an error prevents the rest of the stage from running—such as, for example, a compilation error. Otherwise, we run the commit stage to the end and present an aggregated report of all the errors and failures so they can all be fixed at once.

什么应该打破提交阶段?

What Should Break the Commit Stage?

传统上,提交阶段被设计为在上面列出的情况之一中失败:编译失败、测试中断或存在环境问题。否则,提交阶段成功,报告一切正常。但是,如果因为只有少数测试而通过了测试怎么办?如果代码质量不好怎么办?如果编译成功但是有几百个warning,我们是不是应该满意了?绿色提交阶段很容易成为误报,表明应用程序的质量是可以接受的,但实际上并非如此。

Traditionally, the commit stage is designed to fail in one of the circumstances listed above: Compilation fails, tests break, or there is an environmental problem. Otherwise, the commit stage succeeds, reporting that everything is OK. But what if the tests pass because there are only a handful of them? What if the quality of the code is bad? If compilation succeeds but there are hundreds of warnings, should we be satisfied? A green commit stage can easily be a false positive, suggesting that the quality of the application is acceptable when in fact it isn’t.

可以提出一个强有力的论据,即我们对提交阶段施加的二元约束(成功或失败)过于局限。在提交阶段运行完成后,应该可以提供更丰富的信息,例如一组表示代码覆盖率和其他指标的图表。可以使用一系列阈值将此信息聚合到交通灯显示(红色、琥珀色、绿色)或滑动比例中。例如,如果单元测试覆盖率低于 60%,我们可以让提交阶段失败,如果它低于 80%,我们可以让它通过但状态为琥珀色,而不是绿色。

A strong argument can be made that the binary constraint we impose upon the commit stage—either success or a failure—is too limiting. It should be possible to provide richer information, such as a set of graphs representing code coverage and other metrics, upon completion of a commit stage run. This information could be aggregated using a series of thresholds into a traffic lights display (red, amber, green) or a sliding scale. We could, for example, fail the commit stage if the unit test coverage drops below 60%, and have it pass but with the status of amber, not green, if it goes below 80%.

我们在现实生活中从未见过如此复杂的东西。但是,我们已经编写了提交阶段脚本,如果警告数量增加或减少失败(我们称之为“棘轮”的做法),如“警告和代码样式违规导致构建失败”部分中所述,它会导致失败在第 73 页如果重复数量增加超过某个预设限制,或者由于其他一些违反代码质量的情况,让您的脚本在提交阶段失败是完全可以接受的。

We haven’t seen anything this sophisticated in real life. However, we have written commit stage scripts which cause it to fail if the number of warnings increases, or fails to decrease (a practice we call “ratcheting”), as described in the “Failing the Build for Warnings and Code Style Breaches” section on page 73. It is perfectly acceptable to have your scripts fail the commit stage if the amount of duplication increases beyond some preset limit, or for some other violation of code quality.

但请记住,如果提交阶段失败,规则是交付团队必须立即停止他们正在做的任何事情并修复它。不要因为一些没有得到整个团队一致同意的原因而导致提交测试失败,否则人们将不再认真对待失败,持续集成将崩溃。但是,请务必持续检查您的应用程序的质量,并考虑在适当的时候通过提交阶段实施质量指标。

Remember, though, that if the commit stage fails, the rule is that the delivery team must immediately stop whatever they are doing and fix it. Don’t fail the commit test for some reason that hasn’t been agreed upon by the whole team, or people will stop taking failures seriously and continuous integration will break down. Do, however, continuously review your application’s quality and consider enforcing quality metrics through the commit stage where appropriate.

小心照料提交阶段

Tend the Commit Stage Carefully

提交阶段将包括构建脚本和运行单元测试的脚本、静态分析工具等。这些脚本需要小心维护并以与对待申请的任何其他部分相同的尊重程度对待。与任何其他软件系统一样,当构建脚本的设计和维护不当时,保持它们正常工作的努力似乎呈指数级增长。这具有双重打击效果。糟糕的构建系统不仅会浪费宝贵且昂贵的开发工作,使他们无法完成创建应用程序业务行为的重要工作;它还会减慢仍在尝试实施该业务行为的任何人的速度。我们已经看到几个项目在构建问题的重压下实际上陷入停顿。

The commit stage will include both build scripts and scripts to run unit tests, static analysis tools, and so forth. These scripts need to be maintained carefully and treated with the same level of respect as you would treat any other part of your application. Like any other software system, when build scripts are poorly designed and maintained, the effort to keep them working grows in what seems like an exponential manner. This has a double-whammy effect. A poor build system not only draws valuable and expensive development effort away from the important job of creating the business behavior of your application; it also slows down anyone still trying to implement that business behavior. We have seen several projects effectively grind to a halt under the weight of their build problems.

随着脚本的发展,在提交阶段不断努力提高脚本的质量、设计和性能。高效、快速、可靠的提交阶段是任何开发团队生产力的关键推动因素,因此在时间和思想上的少量投资几乎总是很快就能得到回报。保持快速提交构建并确保及早发现任何类型的故障需要创造力,例如仔细选择和设计测试用例。被视为次要于应用程序代码的脚本很快变得无法理解和维护。到目前为止,我们的记录是我们继承了一个包含 10,000 行 XML 的 Ant 脚本的项目。不用说,这个项目需要整个团队致力于保持构建正常运行——这完全是资源浪费。

Constantly work to improve the quality, design, and performance of the scripts in your commit stage as they evolve. An efficient, fast, reliable commit stage is a key enabler of productivity for any development team, so a small investment in time and thought to get it working well is nearly always repaid very quickly. Keeping the commit build fast and ensuring that failures, of whatever kind, are caught early requires creativity, such as careful selection and design of test cases. Scripts that are treated as secondary to application code rapidly become impossible to understand and maintain. Our record so far is a project we inherited with an Ant script weighing in at 10,000 lines of XML. Needless to say, this project required an entire team devoted to keeping the build working—a complete waste of resources.

确保您的脚本是模块化的,如第 6 章“构建和部署脚本”中所述。构建它们以便将经常使用但很少更改的常见任务与您经常更改的任务分开,例如向代码库添加新模块。将运行部署管道不同阶段的代码分离到单独的脚本中。最重要的是,避免特定于环境的脚本:将特定于环境的配置与构建脚本本身分开。

Ensure that your scripts are modular, as described in Chapter 6, “Build and Deployment Scripting.” Structure them so as to keep common tasks, used all the time but rarely changing, separate from the tasks that you will be changing often, such as adding a new module to your codebase. Separate the code that runs different stages of your deployment pipeline into separate scripts. Most importantly of all, avoid environment-specific scripts: Separate environment-specific configuration from the build scripts themselves.

赋予开发者所有权

Give Developers Ownership

在一些组织中,有专家团队,他们擅长创建有效的模块化构建管道以及管理管道运行的环境。我们都担任过这个角色。但是,如果我们到了只有那些专家才能维护 CI 系统的地步,我们认为这是失败的。

At some organizations, there are teams of specialists who are experts at the creation of effective, modular build pipelines and the management of the environments in which they run. We have both worked in this role. However, we consider it a failure if we get to the point where only those specialists can maintain the CI system.

交付团队对提交阶段(以及管道基础设施的其余部分)有一种主人翁意识是至关重要的。它与他们的工作和生产力密切相关。如果你在开发人员和他们快速有效地进行更改的能力之间设置任何障碍,你将减慢他们的进度并为以后留下麻烦。

It is vital that the delivery team have a sense of ownership for the commit stage (and indeed the rest of the pipeline infrastructure). It is intimately tied to their work and their productivity. If you impose any barriers between the developers and their ability to get changes made quickly and effectively, you will slow their progress and store trouble for later.

普通的更改,例如添加新的库、配置文件等,应该由开发人员和运维人员在发现需要时一起工作来执行。这种活动不应该由构建完成专家,除非在项目的最早期,当团队正在努力建立构建时。

Run-of-the-mill changes, such as adding new libraries, configuration files, and so on, should be perormed by developers and operations people working together as they find the need to do so. This kind of activity should not be done by a build specialist, except perhaps in the very early days of a project when the team is working to establish the build.

不应低估专家的专业知识,但他们的目标应该是建立良好的结构、模式和技术使用,并将他们的知识转移到交付团队。一旦建立了这些基本规则,他们的专业知识应该只需要用于重大的结构转变,而不是日常的日常构建维护。

The expertise of specialists is not to be undervalued, but their goal should be to establish good structures, patterns, and use of technology, and to transfer their knowledge to the delivery team. Once these ground rules are established, their specialist expertise should only be needed for significant structural shifts, not regular day-to-day build maintenance.

对于非常大的项目,有时有足够的工作来保持环境或构建专家全职忙碌,但根据我们的经验,最好将其视为解决棘手问题的临时权宜之计,由此产生的知识将通过开发人员与专家合作的交付团队。

For very large projects, there is sometimes enough work to keep an environment or build specialist busy full time, but in our experience this is best treated as a temporary stop-gap to solve a thorny problem, with the resulting knowledge to be propagated through the delivery team by developers working with the specialist.

开发人员和运维人员必须对构建系统的维护感到满意并负责。

Developers and operations people must feel comfortable with, and responsible for, the maintenance of their build system.

为超大型团队使用 Build Master

Use a Build Master for Very Large Teams

在最多 20 或 30 人的小型和同一地点的团队中,自组织可以很好地发挥作用。如果构建失败,在这种规模的团队中,通常很容易找到负责的人,如果他们不在处理它,则提醒他们这个事实,或者在他们处理的时候提供帮助。

In small and colocated teams of up to twenty or thirty individuals, selforganization can work very well. If the build is broken, in a team this size it is usually easy enough to locate the person or persons responsible and either remind them of the fact if they are not working on it, or offer to help if they are.

在更大或分布更广的团队中,这并不总是那么容易。在这种情况下,让某人扮演“建造大师”的角色是很有用的。他们的工作是监督和指导构建的维护,同时也鼓励和执行构建纪律。如果构建中断,构建主管会注意到并温和地——或者如果已经有一段时间则不会温和地——提醒他们对团队负责的罪魁祸首,以快速修复构建或撤消他们的更改。

In larger or more widely spread teams, this isn’t always easy. Under these circumstances it is useful to have someone to play the role of a “build master.” Their job is to oversee and direct the maintenance of the build, but also to encourage and enforce build discipline. If a build breaks, the build master notices and gently—or not gently if it has been a while—reminds the culprit of their responsibility to the team to fix the build quickly or back out their changes.

我们发现此角色有用的另一种情况是在刚接触持续集成的团队中。在这样的团队中,建立纪律尚未根深蒂固,因此需要提醒以保持事情的正常进行。

Another situation where we have found this role useful is in teams new to continuous integration. In such teams, build discipline is not yet ingrained, so reminders are needed to keep things on track.

build master 永远不应该是一个永久性的角色。团队成员应该轮流使用它,也许每周一次。对于每个人来说,不时尝试这个角色是一种很好的纪律——也是一种重要的学习经历。无论如何,想要全职做这件事的人少之又少。

The build master should never be a permanent role. Team members should rotate through it, perhaps on a weekly basis. It’s good discipline—and an important learning experience—for everyone to try this role from time to time. In any case, the kind of people who want to do it full time are few and far between.

提交阶段的结果

The Results of the Commit Stage

提交阶段与部署管道中的每个阶段一样,都有输入和输出。输入是源代码,输出是二进制文件和报告。生成的报告包括测试结果,如果测试失败,这些结果对于确定问题出在哪里至关重要,以及来自代码库分析的报告。分析报告可以包括测试覆盖率、圈复杂度、剪切和粘贴分析、传入和传出耦合以及任何其他有助于建立代码库健康状况的有用指标。生成的二进制文件提交阶段与将在整个管道中重用并可能发布给用户的阶段完全相同。

The commit stage, like every stage in the deployment pipeline, has both inputs and outputs. The inputs are source code, and the outputs are binaries and reports. The reports produced include the test results, which are essential to work out what went wrong if the tests fail, and reports from analysis of the codebase. Analysis reports can include things like test coverage, cyclomatic complexity, cut and paste analysis, afferent and efferent coupling, and any other useful metrics that help establish the health of the codebase. The binaries generated by the commit stage are precisely the same ones that will be reused throughout the pipeline, and potentially released to users.

工件存储库

The Artifact Repository

提交阶段的输出,您的报告和二进制文件,需要存储在某个地方以便在您的管道的后期阶段重用,并且您的团队能够掌握它们。明显的地方可能是您的版本控制系统。这不是正确的做法有几个原因,除了附带的事实,即通过这种方式您可能会快速使用磁盘空间,并且某些版本控制系统不支持这种行为。

The outputs of the commit stage, your reports and binaries, need to be stored somewhere for reuse in the later stages of your pipeline, and for your team to be able to get hold of them. The obvious place might appear to be your version control system. There are several reasons why this is not the right thing to do, apart from the incidental facts that in this way you’re likely to work through disk space fast, and that some version control systems won’t support such behavior.

• 工件存储库是一种不常见的版本控制系统,因为它只需要保留一些版本。一旦发布候选在部署管道的某个阶段失败,我们就不再对它感兴趣。因此,如果我们愿意,我们可以从工件存储库中清除二进制文件和报告。

• The artifact repository is an unusual kind of version control system, in that it only needs to keep some versions. Once a release candidate has failed some stage in the deployment pipeline, we are no longer interested in it. So we can, if we wish, purge the binaries and reports from the artifact repository.

• 必须能够从您发布的软件追溯到用于创建它的版本控制中的修订。为此,管道实例应与触发它的版本控制系统中的修订相关联。将任何内容作为管道的一部分检查到源代码管理中,通过引入与管道相关的进一步修订,使此过程变得更加复杂。

• It is essential to be able to trace back from your released software to the revisions in version control that were used to create it. In order to do this, an instance of a pipeline should be correlated with the revisions in your version control system that triggered it. Checking anything into source control as part of your pipeline makes this process significantly more complex by introducing further revisions associated with your pipeline.

• 好的配置管理策略的验收标准之一是二进制创建过程应该是可重复的。也就是说,如果我删除二进制文件,然后从最初触发它的同一修订版重新运行提交阶段,我应该再次获得完全相同的二进制文件。二进制文件在配置管理领域是二等公民,尽管将二进制文件的哈希值保存在永久存储中以验证您是否可以重新创建完全相同的东西并从生产阶段回溯到提交阶段进行审核是值得的。

• One of the acceptance criteria for a good configuration management strategy is that the binary creation process should be repeatable. That is, if I delete the binaries and then rerun the commit stage from the same revision that originally triggered it, I should get exactly the same binaries again. Binaries are second-class citizens in the world of configuration management, although it is worth keeping hashes of your binaries in permanent storage to verify that you can re-create exactly the same thing and to audit back from production to the commit stage.

大多数现代持续集成服务器都提供工件存储库,包括允许在一段时间后清除不需要的工件的设置。它们通常提供一种机制,以声明方式指定您希望在它们运行的​​任何作业之后将哪些工件存储在存储库中,并提供一个 Web 界面以允许您的团队访问报告和二进制文件。或者,您可以使用专用的工件存储库(如 Nexus)或其他一些 Maven 风格的存储库管理器来处理二进制文件(这些通常不适合存储报告)。存储库管理器使从开发机器访问二进制文件变得更加容易,而无需与您的 CI 服务器集成。

Most modern continuous integration servers provide an artifact repository, including settings which allow unwanted artifacts to be purged after some length of time. They generally provide a mechanism to specify declaratively which artifacts you want to store in the repository following any jobs that they run, and provide a web interface to allow your team to access the reports and binaries. Alternatively, you could use a dedicated artifact repository like Nexus, or some other Maven-style repository manager, to handle binaries (these are not generally suitable for storing reports). Repository managers make it much easier to access binaries from development machines without having to integrate with your CI server.

图 7.2 工件存储库的作用

Figure 7.2 The role of the artifact repository

图片

图 7.2显示了在典型安装中使用工件存储库的图表。它是存储每个候选发布版本的二进制文件、报告和元数据的关键资源。

Figure 7.2 shows a diagram of the use of an artifact repository in a typical installation. It is a key resource which stores the binaries, reports, and metadata for each of your release candidates.

以下详细介绍了候选发布成功进入生产的快乐路径中的每个步骤。这些数字指的是图 7.2中所示的枚举步骤。

The following details each step in the happy path of a release candidate that makes it successfully into production. The numbers refer to the enumerated steps shown in Figure 7.2.

1. 你的交付团队中有人做出了改变。

1. Somebody on your delivery team commits a change.

2. 您的持续集成服务器运行提交阶段。

2. Your continuous integration server runs the commit stage.

3. 成功完成后,二进制文件以及任何报告和元数据都会保存到工件存储库中。

3. On successful completion, the binary as well as any reports and metadata are saved to the artifact repository.

4. 然后,您的 CI 服务器检索由提交阶段创建的二进制文件,并将其部署到类似生产的测试环境中。

4. Your CI server then retrieves the binaries created by the commit stage and deploys to a production-like test environment.

5. 您的持续集成服务器然后运行验收测试,重用提交阶段创建的二进制文件。

5. Your continuous integration server then runs the acceptance tests, reusing the binaries created by the commit stage.

6. 成功完成后,发布候选将被标记为已通过验收测试。

6. On successful completion, the release candidate is marked as having passed the acceptance tests.

7. 测试人员可以获得所有已通过验收测试的构建列表,并可以按下按钮运行自动化流程,将它们部署到手动测试环境中。

7. Testers can obtain a list of all builds that have passed the acceptance tests, and can press a button to run the automated process to deploy them into a manual testing environment.

8. 测试人员进行手动测试。

8. Testers perform manual testing.

9. 手动测试成功结束后,测试人员更新候选发布版本的状态,表明它已通过手动测试

9. On successful conclusion of manual testing, testers update the status of the release candidate to indicate it has passed manual testing.

10. 您的 CI 服务器从工件存储库中检索已通过验收测试或手动测试(取决于管道配置)的最新候选者,并将应用程序部署到生产测试环境。

10. Your CI server retrieves the latest candidate that has passed acceptance testing, or manual testing depending on the pipeline configuration, from the artifact repository and deploys the application to the production test environment.

11. 容量测试针对发布候选运行。

11. The capacity tests are run against the release candidate.

12. 如果成功,候选人的状态将更新为“已通过能力测试”。

12. If successful, the status of the candidate is updated to “capacity-tested.”

13. 根据流水线的需要,在多个阶段重复此模式。

13. This pattern is repeated for as many stages as the pipeline requires.

14. 一旦 RC 通过了所有相关阶段,它就“准备好发布”了,并且可以由任何具有适当授权的人发布,通常是 QA 和操作人员签字的组合。

14. Once the RC has passed through all of the relevant stages, it is “ready for release,” and can be released by anybody with the appropriate authorization, usually a combination of sign-off by QA and operations people.

15. 在发布过程结束时,RC 被标记为“已发布”。

15. At the conclusion of the release process, the RC is marked as “released.”


图片

为简单起见,我们将此描述为一个顺序过程。对于早期阶段,这是正确的:它们应该按顺序执行。然而,根据项目的不同,不按顺序运行一些验收后阶段的步骤可能是有意义的。例如,手动测试和容量测试都可以通过验收测试的成功完成来触发。或者,测试团队可以选择将不同的候选发布版本部署到他们的环境中。

For simplicity, we described this as a sequential process. For the early stages this is true: They should be executed in order. However, depending on the project, it may make sense to run some of the post-acceptance-stage steps nonsequentially. For example, manual testing and capacity testing can both be triggered by the successful completion of the acceptance tests. Alternatively, the testing team can choose to deploy different release candidates to their environments.


提交测试套件原则和实践

Commit Test Suite Principles and Practices

有一些重要的原则和实践来管理提交测试套件的设计。绝大多数提交测试都应该由单元测试组成,而这正是我们在本节中关注的重点。单元测试最重要的特性是它们的执行速度应该非常快。如果套件不够快,有时我们会导致构建失败。第二个重要的属性是它们应该覆盖代码库的大部分(大约 80% 是一个很好的经验法则),让您有足够的信心,当它们通过时,应用程序很可能会正常工作。当然,每个单元测试只测试应用程序的一小部分而不启动它——因此,根据定义,单元测试套件不能让您完全相信您的应用程序可以正常工作;这就是其余部署管道的用途。

There are some important principles and practices governing the design of a commit test suite. The vast majority of your commit tests should be comprised of unit tests, and it is these that we focus on in this section. The most important property of unit tests is that they should be very fast to execute. Sometimes we fail the build if the suite isn’t sufficiently fast. The second important property is that they should cover a large proportion of the codebase (around 80% is a good rule of thumb), giving you a good level of confidence that when they pass, the application is fairly likely to be working. Of course, each unit test only tests a small part of the application without starting it up—so, by definition, the unit test suite can’t give you full confidence that your application works; that’s what the rest of the deployment pipeline is for.

图 7.3 测试自动化金字塔(Cohn,2009 年,第 15 章

Figure 7.3 Test automation pyramid (Cohn, 2009, Chapter 15)

图片

Mike Cohn 有一个很好的方法来可视化你应该如何构建你的自动化测试套件。在他的测试自动化金字塔中,如图 7.3所示,单元测试构成了绝大多数测试。但由于它们执行得如此之快,单元测试套件仍应在几分钟内完成。尽管验收测试较少(这些测试进一步细分为服务和 UI 测试),但这些测试通常需要更长的时间才能执行,因为它们是针对完整的运行系统运行的。所有级别对于确保应用程序正常工作并提供预期的业务价值都是必不可少的。该测试金字塔覆盖了第 84 页“测试类型”部分所示的测试象限图(“支持编程”)的左侧

Mike Cohn has a good way of visualizing how you should structure your automated test suite. In his test automation pyramid, shown in Figure 7.3, the unit tests form the vast majority of the tests. But since they execute so fast, the unit test suite should still complete in just a few minutes. Even though there are fewer acceptance tests (these are further subdivided into service and UI tests), these will typically take far longer to execute because they run against the full running system. All levels are essential to ensure that the application is working and delivering the expected business value. This test pyramid covers the left-hand side of the testing quadrant diagram (“support programming”) shown in the “Types of Tests” section on page 84.

设计将快速运行的提交测试并不总是那么简单。我们将在接下来的几段中描述几种策略。不过,它们中的大多数都是实现单一目标的技术:最小化任何给定测试的范围,并尽可能专注于仅测试系统的一个方面。特别是,运行单元测试不应触及文件系统、数据库、库、框架或外部系统。对环境中这些部分的任何调用都应替换为测试替身,例如模拟和存根(测试替身的类型在第 91 页的“测试替身”部分中定义)。关于单元测试和测试驱动开发已经写了很多,所以我们在这里只触及表面。查看参考书目以获取有关此主题的更多信息。1个

Designing commit tests that will run quickly isn’t always simple. There are several strategies that we will describe in the next few paragraphs. Most of them, though, are techniques to achieve a single goal: to minimize the scope of any given test and keep it as focused as possible on testing only one aspect of the system. In particular, running unit tests shouldn’t touch the filesystem, databases, libraries, frameworks, or external systems. Any calls to these parts of your environment should be replaced with test doubles, such as mocks and stubs (types of test doubles are defined in the “Test Doubles” section on page 91). A lot has been written about unit testing and test-driven development, so we’re only scratching the surface here. Check out the bibliography for more on this topic.1

避免用户界面

Avoid the User Interface

根据定义,用户界面是用户发现错误的最明显的地方。因此,自然而然地倾向于将测试工作集中在它上面,有时是以牺牲其他类型的测试为代价的。

The user interface is, by definition, the most obvious place where your users will spot bugs. As a result, there is a natural tendency to focus test efforts on it, sometimes at the cost of other types of testing.

但是,出于提交测试的目的,我们建议您根本不要通过 UI 进行测试。UI 测试的困难是双重的。首先,它往往涉及被测软件的许多组件或级别。这是有问题的,因为在执行测试本身之前,需要付出努力和时间才能让所有的部分都为测试做好准备。其次,UI 被设计为在人类时间尺度上工作,与计算机时间尺度相比,人类时间尺度非常慢。

For the purposes of commit tests, though, we recommend that you don’t test via the UI at all. The difficulty with UI testing is twofold. First, it tends to involve a lot of components or levels of the software under test. This is problematic because it takes effort, and so time, to get all of the pieces ready for the test before executing the test itself. Second, UIs are designed to work at human timescales which, compared to computer timescales, are desperately slow.

如果您的项目或技术选择允许您避免这两个问题,也许值得创建通过 UI 操作的单元级测试,但根据我们的经验,UI 测试通常是有问题的,通常在验收测试阶段更好地处理部署管道。

If your project or technology choice allows you to avoid both of these issues, perhaps it is worth creating unit-level tests that operate via the UI, but in our experience UI testing is often problematic and usually better handled at the acceptance test stage of the deployment pipeline.

我们将在关于验收测试的章节中更详细地讨论 UI 测试的方法。

We will discuss approaches to UI testing in much more detail in our chapter on acceptance testing.

使用依赖注入

Use Dependency Injection

依赖注入或控制反转是一种设计模式,它描述了应该如何从对象外部而不是从对象内部建立对象之间的关系。显然,此建议仅适用于使用面向对象语言的情况。

Dependency injection, or inversion of control, is a design pattern that describes how the relationships between objects should be established from outside the objects rather than from within. Obviously, this advice only applies if you’re using an object-oriented language.

如果我创建一个Car类,我可以设计它,以便在我创建新Car时它创建自己的Engine或者,我可以选择设计汽车,以便它迫使我在创建它时为其提供引擎。

If I create a Car class, I could design it so it creates its own Engine whenever I create a new Car. Alternately, I can elect to design the Car so that it forces me to provide it with an Engine when I create it.

后者是依赖注入。这更加灵活,因为现在我可以在不更改Car代码的情况下创建具有不同类型Engine的Car 。我什至可以用一个特殊的TestEngine创建我的Car ,它只在我测试Car时假装是一个Engine

The latter is dependency injection. This is more flexible because now I can create Cars with different kinds of Engine without changing the Car code. I could even create my Car with a special TestEngine that only pretends to be an Engine while I’m testing the Car.

这种技术不仅是通向灵活、模块化软件的好途径,而且还可以很容易地将测试范围限制在您要测试的类,而不是它们的所有相关包袱。

This technique is not only a great route to flexible, modular software, but it also makes it very easy to limit the scope of a test to just the classes that you want to test, not all of their dependent baggage too.

避免数据库

Avoid the Database

刚开始使用自动化测试的人通常会编写与代码中的某个层交互的测试,将结果存储在数据库中,然后确认结果已存储。虽然这种方法的优点是易于理解,但在所有其他方面它并不是一种非常有效的方法。

People new to the use of automated testing will often write tests that interact with some layer in their code, store the results in the database, and then confirm that the results were stored. While this has the advantage of being a simple approach to understand, in all other respects it isn’t a very effective approach.

首先,它生成的测试运行起来要慢得多。当您想要重复测试或连续运行多个类似测试时,测试的状态性可能会成为一个障碍。基础设施设置的复杂性使得整个测试方法的建立和管理更加复杂。最后,如果从测试中消除数据库并不简单,则意味着代码中的分层和关注点分离不佳。这是可测试性和 CI 对您和您的团队施加微妙压力以开发更好代码的另一个领域。

First of all, the tests it produces are dramatically slower to run. The statefulness of the tests can be a handicap when you want to repeat them, or run several similar tests in close succession. The complexity of the infrastructure setup makes the whole testing approach more complex to establish and manage. Finally, if it isn’t simple to eliminate the database from your tests, it implies poor layering and separation of concerns in your code. This is another area where testability and CI apply a subtle pressure on you and your team to develop better code.

构成大部分提交测试的单元测试不应该依赖于数据库。为此,您应该能够将被测代码与其存储分开。这需要在代码中进行良好的分层,以及使用依赖注入等技术,甚至是内存数据库作为最后的手段。

The unit tests that form the bulk of your commit tests should never rely on the database. To achieve this, you should be able to separate the code under test from its storage. This requires good layering in the code, as well as the use of techniques like dependency injection or even an in-memory database as a last resort.

但是,您还应该在提交测试中包含一两个非常简单的冒烟测试。这些应该是验收测试套件中的端到端测试,用于测试高价值、常用的功能并证明您的应用程序实际运行。

However, you should also include one or two very simple smoke tests in your commit tests. These should be end-to-end tests from your acceptance test suite that test high-value, commonly used functionality and prove that your application actually runs.

避免单元测试中的异步

Avoid Asynchrony in Unit Tests

单个测试用例范围内的异步行为使系统难以测试。最简单的方法是通过拆分测试来避免异步,这样一个测试运行到异步中断点,然后另一个单独的测试开始。

Asynchronous behaviors within the scope of a single test case make systems difficult to test. The simplest approach is to avoid the asynchrony by splitting your tests so that one test runs up to the point of the asynchronous break and then a separate test starts.

例如,如果您的系统发布一条消息然后对其进行操作,请使用您自己的接口包装原始消息发送技术。然后您可以在一个测试用例中确认调用是按照您的预期进行的,可能使用实现消息传递接口的简单存根或使用下一节中描述的模拟。您可以添加第二个测试来验证消息处理程序的行为,只需调用通常由消息传递基础结构调用的点。但是,有时,根据您的体系结构,如果不进行大量工作,这是不可能的。

For example, if your system posts a message and then acts on it, wrap the raw message-sending technology with an interface of your own. Then you can confirm that the call is made as you expect in one test case, perhaps using a simple stub that implements the messaging interface or using mocking as described in the next section. You can add a second test that verifies the behavior of the message handler, simply calling the point that would be normally called by the messaging infrastructure. Sometimes, though, depending on your architecture, this is not possible without a lot of work.

我们建议您非常努力地消除提交阶段测试中的异步性。依赖于基础设施的测试,例如消息传递(甚至在内存中),算作组件测试,而不是单元测试。更复杂、运行更慢的组件测试应该是验收测试阶段的一部分,而不是提交阶段。

We recommend that you work very hard to eliminate asynchrony in the commit stage testing. Tests which rely on infrastructure, such as messaging (even in-memory), count as component tests, not unit tests. More complex, slower-running component tests should be part of your acceptance test stage, not commit stage.

使用测试替身

Using Test Doubles

理想的单元测试侧重于少量、密切相关的代码组件,通常是单个类或几个密切相关的类。

The ideal unit tests are focused on a small, closely related number of code components, typically a single class or a few closely related classes.

然而,在一个设计良好的系统中,每个类的规模都相对较小,并且通过与其他类的交互来实现其目标。这是良好封装的核心——每个类都对彼此如何实现其目标保密。

However, in a well-designed system, each class is relatively small in size and achieves its goals through interactions with other classes. This is at the heart of good encapsulation—each class keeping secrets from every other about how it achieves its goals.

问题是,在这样一个设计良好的模块化系统中,在关系网络中间测试一个对象可能需要在所有周围的类中进行冗长的设置。解决方案是伪造与类的依赖项的交互。

The problem is that, in such a nicely designed modular system, testing an object in the middle of a network of relationships may require lengthy setup in all the surrounding classes. The solution is to fake the interactions with a class’ dependents.

剔除此类依赖项的代码有着悠久而光荣的传统。我们已经描述了依赖注入的使用,并提供了一个简单的当我们建议使用TestEngine代替Engine时存根的例子。

Stubbing out the code of such dependencies has a long and honorable tradition. We have already described the use of dependency injection and provided a simple example of stubbing when we suggested the use of a TestEngine in place of an Engine.

Stubbing 是用提供固定响应的模拟版本替换系统的一部分。存根不响应编程之外的任何内容。这是一种强大而灵活的方法,在每个级别都很有用——从对被测代码所依赖的单个简单类进行存根,到对整个系统进行存根。

Stubbing is the replacement of a part of a system with a simulated version that provides canned responses. Stubs don’t respond to anything outside what is programmed. This is a powerful and flexible approach that is useful at every level—from stubbing a single, simple class that your code under test depends upon, to stubbing an entire system.

我们倾向于将存根广泛用于大型组件和子系统,但对于编程语言级别的组件则较少;在这个层面上,我们通常更喜欢嘲笑。

We tend to use stubbing widely for large-scale components and subsystems, but less so for the components at the programming language level; at this level, we generally prefer mocking.

模拟是一种较新的技术。它的动机是喜欢存根,并希望广泛使用它们,而无需编写大量存根代码。如果我们可以让计算机自动为我们构建一些存根,而不是编写乏味的代码来存根我们正在测试的类的所有依赖关系,那不是很好吗?

Mocking is a newer technique. It is motivated by a liking for stubs, and a desire to use them widely, without incurring the work of writing lots of stub code. Wouldn’t it be wonderful if, instead of writing tedious code to stub out all the dependencies for the classes we are testing, we could just let the computer build some stubs for us automatically?

嘲笑本质上就是这样。有几个模拟工具集,例如 Mockito、Rhino、EasyMock、JMock、NMock、Mocha 等等。Mocking 允许你有效地说,“为我构建一个可以伪装成 X 类型类的对象。”

Mocking is essentially just that. There are several mocking toolsets, such as Mockito, Rhino, EasyMock, JMock, NMock, Mocha, and so forth. Mocking allows you to effectively say, “Build me an object that can pretend to be a class of type X.”

至关重要的是,它然后更进一步,允许您在一些简单的断言中指定您期望从正在测试的代码中获得的行为。这是 mocking 和 stubbing 的本质区别——有了存根,我们不关心如何存根被调用;使用模拟,我们可以验证我们的代码是否以我们预期的方式与模拟交互。

Crucially, it then goes further and allows you to specify, in a few simple assertions, the behavior you expect from the code you are testing. This is the essential difference between mocking and stubbing—with stubs, we don’t care about how the stub is called; with mocks, we can verify that our code interacted with the mocks in the way we expected.

让我们回到我们的Car示例并并排考虑这两种方法。为了我们的例子,考虑一个要求,当我们调用Car.drive时,我们期望调用Engine.startEngine.accelerate

Let us return to our Car example and consider the two approaches side by side. For the sake of our example, consider a requirement that when we call Car.drive, we expect that Engine.start followed by Engine.accelerate are called.

正如我们已经描述的,我们将在这两种情况下使用依赖注入来将EngineCar相关联。我们的简单类可能如下所示:

As we have already described, we will use dependency injection in both cases to associate Engines with Cars. Our simple classes might look like this:

图片

如果我们使用存根,我们将创建一个存根实现,一个TestEngine ,它将记录Engine.startEngine.accelerate都被调用的事实由于我们要求首先调用Engine.start ,如果首先调用Engine.accelerate,我们可能应该在存根中抛出异常,或者以某种方式记录错误

If we use stubbing, we will create a stub implementation, a TestEngine that will record the fact that both Engine.start and Engine.accelerate were called. Since we require that Engine.start should be called first, we should probably throw an exception in our stub, or somehow record the error, if Engine.accelerate is called first.

我们的测试现在将包括创建一个新的Car,将TestEngine传递到其构造函数中,调用Car.drive方法,并确认Engine.startEngine.accelerate分别被依次调用。

Our test will now consist of the creation of a new Car passing a TestEngine into its constructor, calling the Car.drive method, and confirming that Engine.start and Engine.accelerate were each called in turn.

图片

使用模拟工具的等效测试更像是这样:我们通过调用模拟类创建模拟Engine,将引用传递给接口或定义接口的类Engine

The equivalent test using mocking tools would be more like this: We create a mock Engine by making a call to a mock class, passing a reference to the interface or class that defines the interface to Engine.

我们声明了两个期望,以正确的顺序指定我们期望Engine.startEngine.accelerate被调用。最后,我们要求模拟系统验证我们预期发生的事情确实发生了。

We declare two expectations specifying, in the correct order, that we expect Engine.start and Engine.accelerate to be called. Finally, we ask the mock system to verify that what we expected to happen actually happened.

图片

这里的示例基于使用名为 JMock 的开源模拟系统,但其他类似。在这种情况下,最后的验证步骤是在每个测试方法的末尾隐式完成的。

The example here is based on the use of an open source mock system called JMock, but others are similar. In this case, the final verification step is done implicitly at the end of each test method.

嘲笑的好处是显而易见的。即使在这个简单过分简化的示例中,代码也少得多。在实际使用中,mocking可以省很多功夫。模拟也是将第三方代码与测试范围隔离开来的好方法。您可以模拟第三方代码的任何接口,从而从测试范围中消除真实代码——当这些交互使用昂贵的远程通信或重量级基础设施时,这是一个极好的举措。

The benefits of mocking are plain. There is considerably less code, even in this trivially oversimplified example. In real use, mocking can save a lot of effort. Mocking is also a great way of isolating third-party code from the scope of your test. You can mock any interfaces to the third-party code and so eliminate the real code from the scope of your test—an excellent move when those interactions use costly remote communications or heavyweight infrastructure.

最后,与所有依赖项和相关状态的组装相比,使用模拟的测试通常非常快。模拟是一种有很多好处的技术:我们强烈推荐给你。

Finally, compared to the assembly of all the dependencies and state associated with them, tests that use mocking are usually very fast. Mocking is a technique that has lots of benefits: We strongly recommend it to you.

最小化测试中的状态

Minimizing State in Tests

理想情况下,您的单元测试应该专注于断言系统的行为。一个常见的问题,尤其是对于有效测试设计的相对新手来说,是测试周围状态的增加。问题实际上是双重的。第一的,很容易设想几乎任何形式的测试,您可以在其中向系统的一个组件输入一些值并返回一些结果。您通过组织相关数据结构来编写测试,以便您可以以正确的形式提交输入并将结果与​​您期望的输出进行比较。事实上,几乎所有的测试都或多或少地采用这种形式。问题在于,如果不小心,系统及其相关测试会变得越来越复杂。

Ideally, your unit tests should focus on asserting the behavior of your system. A common problem, particularly with relative newcomers to effective test design, is the accretion of state around your tests. The problem is really twofold. First, it is easy to envisage a test of almost any form where you input some values to one component of your system and get some results returned. You write the test by organizing the relevant data structures so that you can submit the inputs in the correct form and compare the results with the outputs you expect. In fact, virtually all tests are of this form, to a greater or lesser extent. The problem is that without care, systems and their associated tests become more and more complex.

为了支持您的测试,很容易陷入构建复杂、难以理解且难以维护的数据结构的陷阱。理想的测试是快速和容易设置的,甚至更快和更容易拆除。结构良好的代码往往有简洁的测试。如果您的测试看起来繁琐复杂,这反映了您系统的设计。

It is too easy to fall into the trap of building elaborate, hard to understand, and hard to maintain data structures in order to support your tests. The ideal test is quick and easy to set up and even quicker and easier to tear down. Well-factored code tends to have neat tests. If your tests look cumbersome and complex, it reflects on the design of your system.

不过,这是一个很难解决的问题。我们的建议是尽量减少测试中对状态的依赖。您永远不可能真正消除它,但明智的做法是始终关注您需要构建的环境的复杂性,以便运行您的测试。随着测试变得越来越复杂,很可能表明需要查看代码的结构。

This is a difficult problem to nail, though. Our advice is to work to minimize the dependency on state in your tests. You can never realistically eliminate it, but it is sensible to maintain a constant focus on the complexity of the environment that you need to construct in order to run your test. As the test becomes increasingly complex, it is most likely signaling a need to look at the structure of your code.

假装时间

Faking Time

出于多种原因,时间可能成为自动化测试中的一个问题。也许您的系统需要在晚上 8点触发一天结束的流程可能需要等待 500 毫秒才能进行下一步。也许它需要在闰年的 2 月 29 日做一些不同的事情。

Time can be a problem in automated testing for several reasons. Perhaps your system needs to trigger an end-of-day process at 8 P.M. Perhaps it needs to wait 500 milliseconds before progressing with the next step. Perhaps it needs to do something different on February 29th of a leap year.

所有这些情况都很难处理,如果您试图将它们与真实的系统时钟联系起来,则可能对您的单元测试策略造成灾难性的后果。

All of these cases can be tricky to deal with, and potentially disastrous for your unit-testing strategy if you try to tie them to the real system clock.

我们对任何基于时间的行为的策略是将我们对时间信息的需求抽象为一个在我们控制下的单独类。我们通常应用依赖注入来为我们使用的系统级时间行为注入我们的包装器。

Our strategy for any time-based behavior is to abstract our need for time information into a separate class that is under our control. We usually apply dependency injection to inject our wrapper for the system-level time behaviors we use.

这样,我们可以存根或模拟我们的Clock类的行为,或者我们选择的任何合适的抽象。如果我们在测试范围内决定它是闰年或 500 毫秒后,它完全在我们的控制之下。

This way, we can stub or mock the behavior of our Clock class, or whatever suitable abstraction we choose. If we decide, within the scope of a test, that it is a leap year or 500 milliseconds later, it is fully under our control.

对于快速构建,这对于需要延迟或等待的任何行为都是最重要的。构建您的代码,使测试运行期间的所有延迟均为零,以保持良好的测试性能。如果您的单元测试需要真正的延迟,也许值得重新考虑您的代码设计和测试以避免它。

For fast builds, this is most important for any behavior that warrants some delay or wait. Structure your code so that all delays during the test run are zero, to keep test performance good. If your unit test needs a real delay, perhaps it is worth reconsidering the design of your code and test to avoid it.

这在我们自己的开发中已经根深蒂固,以至于如果我们编写任何需要时间的代码,几乎任何容量,我们都希望我们需要抽象我们对系统时间服务的访问,而不是直接从我们的业务逻辑中调用它们.

This has become so ingrained in our own development that, if ever we write any code that needs time in almost any capacity, we expect that we will need to abstract our access to the system time services instead of calling them direct from within our business logic.

蛮力

Brute Force

开发人员总是会争论最快的提交周期。但实际上,这种需求必须与提交阶段识别您可能引入的最常见错误的能力相平衡。这是一个只能通过反复试验才能实现的优化过程。有时,接受较慢的提交阶段比花太多时间优化测试以提高速度或减少它们捕获的错误比例要好。

Developers will always argue for the fastest commit cycle. In reality, though, this need must be balanced with the commit stage’s ability to identify the most common errors that you are likely to introduce. This is an optimization process that can only work through trial and error. Sometimes, it is better to accept a slower commit stage than to spend too much time optimizing the tests for speed or reducing the proportion of bugs they catch.

我们通常的目标是将提交阶段保持在十分钟以内。就我们而言,这几乎是上限。它比理想的时间长,不到五分钟。从事大型项目的开发人员可能会对十分钟的目标犹豫不决,认为它太短了。其他开发团队会认为这是一种过度的妥协,因为他们知道最有效的提交阶段比这快得多。不过,根据我们对许多项目的观察,我们认为这个数字是一个有用的指南。当这个限制被打破时,开发人员开始做两件事,这两件事都会对开发过程产生极其糟糕的影响:他们开始不那么频繁地签入,如果提交阶段花费了大量运行十多分钟后,他们不再关心提交测试套件是否通过。

We generally aim to keep our commit stage at under ten minutes. This is pretty much the upper bound as far as we are concerned. It is longer than the ideal, which is under five minutes. Developers working on large projects may balk at the target of ten minutes, thinking it unachievably low. Other development teams will see this as a compromise that goes too far, knowing that the most efficient commit stage is much faster than this. We consider this number to be a useful guide, though, based on our observations of many projects. When this limit is broken, developers start doing two things, both of which have an extremely bad effect on the development process: They start checking in less frequently and, if the commit stage takes significantly more than ten minutes to run, they stop caring about whether or not the commit test suite passes.

您可以使用两个技巧来使您的提交测试套件运行得更快。首先,将它分成单独的套件并在多台机器上并行运行它们。现代 CI 服务器具有“构建网格”功能,可以非常简单地执行此操作。请记住,计算能力很便宜,而人很贵。及时获得反馈比几台服务器的成本更有价值。您可以使用的第二个技巧是,作为构建优化过程的一部分,将那些长时间运行且不会经常失败的测试推送到您的验收测试阶段。但是请注意,这会导致等待更长时间才能获得有关一组更改是否破坏了这些测试的反馈。

There are two tricks you can use to make your commit test suite run faster. First of all, split it up into separate suites and run them in parallel on several machines. Modern CI servers have “build grid” functionality that makes it extremely straightforward to do this. Remember that computing power is cheap and people are expensive. Getting feedback on time is much more valuable than the cost of a few servers. The second trick you can use is to push, as part of your build optimization process, those tests that are both long-running and don’t often fail out into your acceptance test stage. Note, however, that this results in a longer wait to get feedback on whether a set of changes has broken these tests.

概括

Summary

提交阶段应该专注于一件事:尽可能快地检测系统更改可能引入的最常见故障,并通知开发人员以便他们能够快速解决问题。提交阶段提供的反馈的价值如此之大,因此重要的是要投资以保持其高效运行,最重要的是,要快速运行。

The commit stage should be focused on one thing: detecting, as fast as possible, the most common failures that changes to the system may introduce, and notifying the developers so they can fix the problems quickly. The value of the feedback that the commit stage provides is such that it is important to invest in keeping it working efficiently, and most of all, quickly.

每次有人对应用程序的代码或配置进行更改时,都应运行部署管道的提交阶段。因此,您的开发团队的每个成员每天都会多次练习它。如果构建的性能低于可接受的标准,开发人员的自然倾向是抱怨:让它增长到五分钟以上,抱怨就会开始。重要的是要听取这种反馈并尽一切可能使这个阶段保持快速,同时密切关注真实情况价值——它很快就会失败,因此会提供错误反馈,否则以后修复这些错误的成本会高得多。

The commit stage of your deployment pipeline should be run every time someone introduces a change into your application’s code or configuration. Thus it will be exercised multiple times each day by each member of your development team. The natural tendency of developers is to complain if the performance of the build falls below an acceptable standard: Let it grow to over five minutes and the complaints will start. It is important to listen to this feedback and to do everything possible to keep this stage fast, while keeping an eye on the real value—which is that it fails fast and so provides feedback on errors that would otherwise be much more costly to fix later.

因此,建立提交阶段——一个在每次更改时启动的自动化流程,用于构建二进制文件、运行自动化测试并生成指标——是您在采用持续集成实践的过程中可以做的最起码的事情。提交阶段在交付过程的质量和可靠性方面提供了巨大的进步——假设您遵循持续集成中涉及的其他实践,例如定期检查并在发现任何缺陷后立即修复。虽然这只是部署管道的开始,但它可能会为您带来最大的收益:一种范式转变,可以了解引入更改的确切时间,该更改会破坏您的应用程序并能够立即使其重新运行。

Thus the establishment of a commit stage—an automated process, launched on every change, that builds binaries, runs automated tests, and generates metrics—is the minimum you can do on the way to your adoption of the practice of continuous integration. A commit stage provides a huge advance in the quality and reliability of your delivery process—assuming you follow the other practices involved in continuous integration, such as checking in regularly and fixing any defects as soon as they are discovered. Though it is only the start of the deployment pipeline, it provides perhaps the biggest bang for your buck: a paradigm shift to knowing the exact moment a change is introduced that breaks your application and being able to get it working again right away.

第 8 章自动化验收测试

Chapter 8. Automated Acceptance Testing

介绍

Introduction

图 8.1 验收测试阶段

Figure 8.1 The acceptance test stage

图片

在本章中,我们将更详细地探讨自动化验收测试及其在部署管道中的位置。验收测试是部署管道中的关键阶段:它们使交付团队超越了基本的持续集成。一旦您进行了自动验收测试,您就是在测试应用程序的业务验收标准,即验证它是否为用户提供了有价值的功能。验收测试通常针对通过提交测试的每个软件版本运行。部署流水线验收测试阶段的工作流程如图8.1所示。

In this chapter we will explore automated acceptance testing, and its place in the deployment pipeline, in a little more detail. Acceptance tests are a crucial stage in the deployment pipeline: They take delivery teams beyond basic continuous integration. Once you have automated acceptance tests in place, you are testing the business acceptance criteria of your application, that is, validating that it provides users with valuable functionality. Acceptance tests are typically run against every version of your software that passes the commit tests. The workflow of the acceptance test stage of the deployment pipeline is shown in Figure 8.1.

我们从讨论交付过程中验收测试的重要性开始本章。然后我们深入讨论如何编写有效的验收测试以及如何维护一个有效的验收测试套件。最后,我们将介绍管理验收测试阶段本身的原则和实践。但之前任何一个,我们都应该说明我们所说的验收测试的意思。与功能测试或单元测试不同,验收测试的作用是什么?

We begin the chapter by discussing the importance of acceptance tests within the delivery process. Then we discuss in depth how to write effective acceptance tests and how to maintain an efficient acceptance test suite. Finally, we cover the principles and practices that govern the acceptance test stage itself. But before any of that, we should state what we mean by acceptance testing. What is the role of an acceptance test as distinct from a functional test or a unit test?

单独的验收测试旨在验证故事或需求的验收标准是否已得到满足。验收标准有许多不同的种类;一方面,它们可以是功能性的或非功能性的。非功能性验收标准包括容量、性能、可修改性、可用性、安全性、可用性等。这里的关键点是,当与特定故事或需求相关的验收测试通过时,它们表明其验收标准已得到满足,因此它既完整又有效。

An individual acceptance test is intended to verify that the acceptance criteria of a story or requirement have been met. Acceptance criteria come in many different varieties; for one thing, they can be functional or nonfunctional. Nonfunctional acceptance criteria include things like capacity, performance, modifiability, availability, security, usability, and so forth. The key point here is that when the acceptance tests associated with a particular story or requirement pass, they demonstrate that its acceptance criteria have been met, and that it is thus both complete and working.

验收测试套件作为一个整体既可以验证应用程序是否提供了客户期望的业务价值,又可以防止破坏应用程序现有功能的回归或缺陷。

The acceptance test suite as a whole both verifies that the application delivers the business value expected by the customer and guards against regressions or defects that break preexisting functions of the application.

将验收测试作为一种表明应用程序满足其每项要求的验收标准的方法有一个额外的好处。它让参与交付过程的每个人——客户、测试人员、开发人员、分析师、运营人员和项目经理——思考每个需求的成功意味着什么。我们将在第 195 页的“作为可执行规范的验收标准”部分对此进行更详细的介绍。

The focus on acceptance testing as a means of showing that the application meets its acceptance criteria for each requirement has an additional benefit. It makes everyone involved in the delivery process—customers, testers, developers, analysts, operations personnel, and project managers—think about what success means for each requirement. We will cover this in more detail in the “Acceptance Criteria as Executable Specifications” section on page 195.

如果您来自测试驱动设计背景,您可能想知道为什么这些与我们的单元测试不同。不同之处在于验收测试是面向业务的,而不是面向开发人员的。他们在类似生产的环境中针对应用程序的运行版本一次测试整个故事。单元测试是任何自动化测试策略的重要组成部分,但它们通常无法提供足够高的可信度以确保应用程序可以发布。验收测试的目的是证明我们的应用程序按照客户的意愿运行,而不是按照程序员认为应该的方式运行。单元测试有时可以分享这个焦点,但并非总是如此。单元测试的目的是表明应用程序的单个部分完成了程序员的意图;

If you are from a test-driven design background, you are perhaps wondering why these aren’t the same as our unit tests. The difference is that acceptance tests are business-facing, not developer-facing. They test whole stories at a time against a running version of the application in a production-like environment. Unit tests are an essential part of any automated test strategy, but they usually do not provide a high enough level of confidence that the application can be released. The objective of acceptance tests is to prove that our application does what the customer meant it to, not that it works the way its programmers think it should. Unit tests can sometimes share this focus, but not always. The aim of a unit test is to show that a single part of the application does what the programmer intends it to; this is by no means the same as asserting that a user has what they need.

为什么自动化验收测试必不可少?

Why Is Automated Acceptance Testing Essential?

关于自动验收测试一直存在很多争议。项目经理和客户通常认为它们的创建和维护成本太高——事实上,如果做得不好,它们确实如此。许多开发人员认为,通过测试驱动开发创建的单元测试套件足以防止回归。我们的经验是,正确创建和维护自动验收测试套件的成本远低于执行频繁的手动验收和回归测试的成本,或者发布低质量软件的替代方案的成本。我们还发现,自动验收测试可以捕获单元或组件测试套件(无论多么全面)永远无法捕获的严重问题。

There has always been a great deal of controversy around automated acceptance tests. Project managers and customers often think they are too expensive to create and maintain—which indeed, when done badly, they are. Many developers believe that unit test suites created through test-driven development are enough to protect against regressions. Our experience has been that the cost of a properly created and maintained automated acceptance test suite is much lower than that of performing frequent manual acceptance and regression testing, or that of the alternative of releasing poor-quality software. We have also found that automated acceptance tests catch serious problems that unit or component test suites, however comprehensive, could never catch.

首先,值得指出的是手动验收测试的成本。为防止缺陷被发布,每次发布时都需要对您的应用程序进行验收测试。我们知道有一个组织在每个版本的手动验收测试上花费 3,000,000 美元。这对他们频繁发布软件的能力是极其严重的制约。在任何复杂的应用程序上执行任何值得付出努力的手动测试工作都将非常昂贵。

First of all, it is worth pointing out the costs of manual acceptance testing. To prevent defects from being released, acceptance testing of your application needs to be performed every time it is released. We know of one organization that spends $3,000,000 on manual acceptance testing for every release. This is an extremely serious constraint on their ability to release software frequently. Any manual testing effort worth its salt, when performed on an application of any complexity, is going to be very expensive.

此外,为了履行其捕获回归缺陷的作用,一旦开发完成并且即将发布,就需要将此类测试作为一个阶段执行。因此,手动测试通常发生在项目团队承受着将软件推出门外的巨大压力的时候。因此,通常没有足够的时间来修复在手动验收测试中发现的缺陷。最后,当发现需要复杂修复的缺陷时,很可能会在应用程序中引入进一步的回归问题。1个

Furthermore, to fulfill its role of catching regression defects, such testing needs to be performed as a phase once development is complete and a release is approaching. Thus manual testing usually happens at a time in the project where teams are under extreme pressure to get software out of the door. As a result, insufficient time is normally planned to fix the defects found as part of manual acceptance testing. Finally, when defects are found that require complex fixes, there is a high chance of introducing further regression problems into the application.1

敏捷社区中一些人提倡的一种方法是几乎完全取消自动化验收测试,并编写全面的单元和组件测试套件。这些与其他 XP 实践(例如结对编程、重构以及由客户、分析师和测试人员一起工作的仔细分析和探索性测试)相结合,被一些人认为是成本较高的自动验收测试的替代方案。2个

An approach advocated by some in the agile community is to do away almost entirely with automated acceptance testing and write comprehensive suites of unit and component tests. These, in combination with other XP practices such as pair programming, refactoring, and careful analysis and exploratory testing by customers, analysts, and testers working together, are regarded by some as providing a superior alternative to the cost of automated acceptance tests.2

这个论点有几个缺陷。首先,没有其他类型的测试可以证明应用程序在生产环境中运行时或多或少地提供了用户期望的商业价值。单元和组件测试不测试用户场景,因此无法发现用户在与应用程序交互过程中使应用程序经历一系列状态时出现的各种缺陷。验收测试正是为此而设计的。他们还擅长捕捉线程问题、事件驱动应用程序中的紧急行为,以及由架构错误或环境和配置问题引起的其他类别的错误。这些类型的缺陷很难通过手动测试发现,更不用说单元或组件测试了。

There are several flaws in this argument. First, no other type of test proves that the application, running more or less as it would in production, delivers the business value its users are expecting. Unit and component tests do not test user scenarios, and are thus incapable of finding the kinds of defects that appear when users put the application through a series of states in the course of interacting with it. Acceptance tests are designed exactly for this. They are also great at catching threading problems, emergent behavior in event-driven applications, and other classes of bugs caused by architectural mistakes or environmental and configuration problems. These kinds of defects can be hard to discover through manual testing, let alone unit or component testing.

当您对应用程序进行大规模更改时,验收测试还可以保护您的应用程序。在这种情况下,单元测试和组件测试通常必须与您的域一起进行彻底的更改,从而限制了它们充当应用程序功能捍卫者的能力。只有验收测试能够证明您的应用程序在这样一个过程结束时仍然有效。

Acceptance tests also protect your application when you are making large-scale changes to it. In this scenario, unit tests and component tests will often have to be radically altered along with your domain, limiting their ability to act as defenders of the function of the application. Only acceptance tests are capable of proving your application still works at the end of such a process.

最后,选择放弃自动化验收测试的团队会给测试人员带来更大的负担,他们必须花更多的时间在无聊的事情上和重复回归测试。我们认识的测试人员不赞成这种方法。虽然开发人员可以承担部分负担,但许多编写单元和组件测试的开发人员在发现自己工作中的缺陷方面根本不如测试人员有效。根据我们的经验,在测试人员参与下编写的自动验收测试比开发人员编写的测试更能发现用户场景中的缺陷。

Finally, teams that choose to forgo automated acceptance tests place a much greater burden on testers, who must then spend much more time on boring and repetitive regression testing. The testers that we know are not in favor of this approach. While developers can take on some of this burden, many developers—who write unit and component tests—are simply not as effective as testers at finding defects in their own work. Automated acceptance tests written with the involvement of testers are, in our experience, a great deal better at finding defects in user scenarios than tests written by developers.

人们不喜欢自动化验收测试的真正原因是人们认为它太昂贵了。但是,有可能将自动化验收测试的成本降低到远低于它变得高效且具有成本效益的水平。当针对通过提交测试的每个构建运行自动验收测试时,对软件交付过程的影响是巨大的。首先,由于反馈循环要短得多,缺陷被发现得更快,修复成本也更低。其次,由于测试人员、开发人员和客户需要密切合作以创建良好的自动化验收测试套件,因此他们之间的协作要好得多,每个人都关注应用程序应该交付的业务价值。

The real reason people don’t like automated acceptance testing is that it is perceived as being too expensive. However, it is possible to decrease the cost of automating acceptance testing to well below the level where it becomes efficient and cost-effective. When automated acceptance tests are run against every build that passed the commit tests, the effects on the software delivery process are dramatic. First of all, since the feedback loop is much shorter, defects are found much sooner, when they are cheaper to fix. Second, since testers, developers, and customers need to work closely to create a good automated acceptance test suite, there is much better collaboration between them, and everybody is focused on the business value that the application is supposed to deliver.

有效的基于验收测试的策略还有其他积极的副作用:生产环境。

There are other positive side effects resulting from an effective acceptance-testbased strategy: Acceptance tests work best with well-factored applications which are properly structured to have a thin UI layer, and carefully designed to be able to run on development machines as well as in production environments.

我们将创建和维护有效的自动化验收测试的问题分为四个部分:创建验收测试;创建应用程序驱动层;实施验收测试;维护验收测试套件。在详细介绍之前,我们将简要介绍我们的方法。

We have split the problem of creating and maintaining effective automated acceptance tests into four sections: creating acceptance tests; creating an application driver layer; implementing acceptance tests; and maintaining acceptance test suites. We will briefly introduce our approach before we go into detail.

如何创建可维护的验收测试套件

How to Create Maintainable Acceptance Test Suites

编写可维护的验收测试首先需要仔细注意分析过程。验收测试源自验收标准,因此您的应用程序的验收标准必须在编写时考虑到自动化,并且必须遵循 INVEST 原则,3特别提到对最终用户有价值和可测试。这是另一个微妙但重要的压力,关注自动化验收测试适用于整个开发过程:更好需求的压力。编写糟糕的验收标准的自动化没有解释要开发的功能如何对用户有价值,这是糟糕且难以维护的验收测试套件的主要来源。

Writing maintainable acceptance tests requires, first of all, careful attention to the analysis process. Acceptance tests are derived from acceptance criteria, so the acceptance criteria for your application must be written with automation in mind and must follow the INVEST principles,3 with a particular reference to being valuable to the end user and testable. This is another of those subtle but important pressures that a focus on automated acceptance testing applies to the whole development process: a pressure for better requirements. Automation of badly written acceptance criteria that don’t explain how the functionality to be developed is valuable to users is a major source of poor and hard-to-maintain acceptance test suites.

图 8.2 验收测试中的层

Figure 8.2 Layers in acceptance tests

图片

一旦您有了一组描述要交付给用户的价值的验收标准,下一步就是使它们自动化。自动验收测试应该总是分层的,如图 8.2所示。

Once you have a set of acceptance criteria describing the value to be delivered to users, the next step is to automate them. Automated acceptance tests should always be layered, as shown in Figure 8.2.

验收测试的第一层是验收标准。Cucumber、JBehave、Concordion、Twist 和 FitNesse 等工具允许您将验收标准直接放入测试中并将它们链接到底层实现。但是,如本章后面所述,您还可以采用在 xUnit 测试名称中编码验收标准的方法。然后,您可以直接从 xUnit 测试框架运行您的验收测试。

The first layer in acceptance tests is the acceptance criteria. Tools like Cucumber, JBehave, Concordion, Twist, and FitNesse allow you to put acceptance criteria directly in tests and link them to the underlying implementation. However, as described later in this chapter, you can also take the approach of encoding the acceptance criteria in the names of your xUnit tests. You can then run your acceptance tests directly from the xUnit test framework.

至关重要的是,您的测试实现使用您的领域语言并且不包含如何与应用程序交互的详细信息。直接引用应用程序 API 或 UI 的测试实现是脆弱的,即使是对 UI 的微小更改也会立即破坏所有引用已更改 UI 元素的测试。当单个 UI 元素发生变化时,看到大量此类验收测试套件崩溃的情况并不少见。

It is crucial that your test implementations use your domain language and do not contain details of how to interact with the application. Test implementations that refer directly to the application’s API or UI are brittle, and even small changes to the UI will immediately break all the tests referring to the changed UI element. It’s not uncommon to see huge swathes of such acceptance test suites break when a single UI element changes.

不幸的是,这种反模式非常普遍。大多数测试都是在详细执行级别编写的:“戳这个,戳那个,看这里寻找结果。” 此类测试通常是记录和回放式测试自动化产品的输出,这是自动化验收测试被认为昂贵的主要原因之一。使用此类工具创建的任何验收测试套件都与 UI 紧密耦合,因此非常脆弱。

Unfortunately, this antipattern is very common. Most tests are written at the level of detailed execution: “Poke this, prod that, look here for a result.” Such tests are often the output of record-and-playback-style test automation products, which is one of the main reasons automated acceptance tests are perceived as expensive. Any acceptance test suites created with such tools are tightly coupled to the UI and therefore extremely brittle.

大多数 UI 测试系统提供的操作允许您将数据放入字段、单击按钮以及从页面的指定区域读取结果。这种详细程度最终是必要的,但与测试用例的意义(真正价值)相去甚远。任何给定的验收测试用例旨在断言的行为不可避免地处于非常不同的抽象级别。我们真正想知道的是诸如“如果我下订单,它会被接受吗?”这样的问题的答案。或“如果我超过信用额度,我是否得到正确通知?”

Most UI testing systems provide operations that allow you to put data into fields, click buttons, and read results from specified areas of the page. This level of detail is ultimately necessary, but it is a long way from the meaning—the real value—of a test case. The behavior that any given acceptance test case is intended to assert is, inevitably, at a very different level of abstraction. What we really want to know is answers to questions like “If I place an order, is it accepted?” or “If I exceed my credit limit, am I correctly informed?”

测试实现应该调用一个较低的层,我们称之为应用程序驱动层,以实际与被测系统交互。应用程序驱动层有一个 API,它知道如何执行操作并返回结果。如果您的测试针对应用程序的公共 API 运行,则应用程序驱动程序层知道此 API 的详细信息并调用它的正确部分。如果您的测试针对 GUI 运行,则该层将包含一个窗口驱动程序。在一个结构良好的窗口驱动程序中,给定的 GUI 元素只会被引用几次,这意味着如果它发生变化,只需要更新对它的这些引用。

Test implementations should call through to a lower layer, which we call the application driver layer, to actually interact with the system under test. The application driver layer has an API that knows how to perform actions and return results. If your tests run against your application’s public API, it is the application driver layer that knows the details of this API and calls the right parts of it. If your tests run against the GUI, this layer will contain a window driver. In a well-factored window driver, a given GUI element will only be referenced a handful of times, which mean that if it is changed, only these references to it will need to be updated.

长期维护验收测试需要纪律。必须特别注意保持测试实现的效率和良好的分解,特别是管理状态、处理超时和测试替身的使用。必须重构验收测试套件,因为添加了新的验收标准以确保它们保持一致。

Maintaining acceptance tests over the long term requires discipline. Careful attention must be paid to keep test implementations efficient and well factored, with particular reference to managing state, handling timeouts, and the use of test doubles. Acceptance test suites must be refactored as new acceptance criteria are added to ensure that they remain coherent.

针对 GUI 进行测试

Testing against the GUI

编写验收测试时的一个重要问题是是否直接针对应用程序的 GUI 运行测试。由于我们的验收测试旨在模拟用户与系统的交互,因此理想情况下,我们应该通过系统的用户界面(如果有的话)进行工作。如果我们不通过用户界面进行测试,那么我们就不会测试我们系统的用户在实际交互中将调用的相同代码路径。然而,直接针对 GUI 进行测试存在几个问题:它的快速变化、场景设置的复杂性、测试结果的访问以及不可测试的 GUI 技术。

An important concern when writing acceptance tests is whether or not to run tests directly against the application’s GUI. Since our acceptance tests are intended to simulate user interactions with the system, ideally we should be working via the user interface of the system, if it has one. If we don’t test via the user interface, we are not testing the same code path that the users of our system will invoke in real interactions. However, there are several problems with testing directly against the GUI: its rapid rate of change, the complexity of scenario setup, access to test results, and untestable GUI technologies.

在应用程序开发过程中,用户界面通常会经常更改。如果您的验收测试与您的 UI 耦合,则对 UI 的微小更改很容易破坏您的验收测试套件。这不仅限于应用程序开发期间;由于可用性、拼写更正等方面的改进,它也可能发生在系统的用户测试期间。

The user interface usually changes frequently during the process of application development. If your acceptance tests are coupled to your UI, small changes to the UI can easily break your acceptance test suites. This is not limited to the period of application development; it can also happen during user tests of the system, due to improvements in usability, spelling corrections, and so on.

其次,如果 UI 是进入系统的唯一途径,场景设置可能会很复杂。设置测试用例可能涉及许多交互以使系统进入准备好进行测试本身的状态。在测试结束时,结果可能无法通过 UI 显而易见,因为 UI 可能无法提供对验证测试结果所需的信息的访问。

Secondly, scenario setup can be complex if the UI is the only way into the system. Setting up a test case can involve many interactions to get the system into a state ready for the test itself. At the conclusion of a test, the results may not be readily evident through the UI which may not provide access to the information you need to validate the test results.

最后,一些 UI 技术,尤其是较新的技术,很难自动测试。4重要的是要检查您选择的 UI 技术是否可以通过自动化框架来驱动。

Finally, some UI technologies, especially newer ones, are extremely hard to test automatically.4 It is important to check that the UI technology you choose can be driven through an automated framework.

有一种替代方法可以通过 GUI 进行测试。如果您的应用程序设计良好,则 GUI 层代表一个明确定义的仅显示代码集合,不包含其自身的任何业务逻辑。在这种情况下,绕过它并针对它下面的代码层编写测试相关的风险可能相对较小。考虑到可测试性而编写的应用程序将具有一个 API,GUI 和测试工具都可以与之对话以驱动应用。直接针对业务层运行测试是一种合理的策略,如果您的应用程序可以支持的话,我们建议您这样做。它只需要您的开发团队有足够的纪律,使演示仅专注于像素绘画,而不是误入业务或应用程序逻辑领域。

There is an alternative to testing through the GUI. If your application is well designed, the GUI layer represents a clearly defined collection of display-only code that doesn’t contain any business logic of its own. In this case, the risk associated with bypassing it and writing tests against the layer of code beneath it may be relatively small. An application written with testability in mind will have an API that both the GUI and the test harness can talk to in order to drive the application. Running tests against the business layer directly is a reasonable strategy that we recommend if your application can sustain it. It only requires enough discipline in your development team to keep the presentation focused solely on pixel-painting and not straying into the realms of business or application logic.

如果您的应用程序不是以这种方式设计的,您将必须直接针对 UI 进行测试。我们将在本章后面讨论管理它的策略,主要策略是窗口驱动程序模式。

If your application is not designed in this way, you’ll have to test directly against the UI. We’ll discuss strategies for managing this later in the chapter, the main strategy being the window driver pattern.

创建验收测试

Creating Acceptance Tests

在本节中,我们将讨论如何创建自动化验收测试。我们将从一起工作的分析师、测试人员和客户确定验收标准开始,然后讨论以可以自动化的形式表示验收标准。

In this section, we will discuss how to create automated acceptance tests. We’ll start with the identification of acceptance criteria by analysts, testers, and customers working together, and then talk about representing acceptance criteria in a form that can be automated.

分析师和测试人员的角色

The Role of Analysts and Testers

您的开发过程应该根据您的个别项目的需要进行定制,但作为一般化,我们建议任何规模的大多数项目都应该有一名业务分析师作为每个团队的一部分。业务分析师的角色主要是代表系统的客户和用户。他们与客户一起确定需求并确定需求的优先级。他们与开发人员合作,以确保他们从用户的角度对应用程序有很好的理解。他们指导开发人员确保故事能够提供他们想要的商业价值。他们与测试人员合作以确保正确指定验收标准,并确保开发的功能满足这些验收标准并交付预期价值。

Your development process should be tailored to suit the needs of your individual project, but as a generalization we recommend that most projects of any size should have a business analyst working as part of each team. The role of the business analyst is primarily to represent the customers and users of the system. They work with the customer to identify and prioritize requirements. They work with the developers to ensure that they have a good understanding of the application from the user’s perspective. They guide developers to ensure that stories deliver the business value that they are meant to. They work with testers to ensure that acceptance criteria are specified properly, and to ensure that functionality developed meets these acceptance criteria and delivers the expected value.

测试人员对任何项目都是必不可少的。他们的作用最终是确保交付团队中的每个人(包括客户)都了解正在开发的软件的当前质量和生产准备情况。他们通过与客户和分析师合作定义故事或需求的验收标准,与开发人员合作编写自动验收测试,以及执行手动测试活动(例如探索性测试、手动验收测试和展示)来实现这一点。

Testers are essential on any project. Their role is ultimately to ensure that the current quality and production-readiness of the software being developed is understood by everybody on the delivery team, including the customer. They do this through working with customers and analysts to define acceptance criteria for stories or requirements, working with developers to write automated acceptance tests, and performing manual testing activities such as exploratory testing, manual acceptance testing, and showcases.

并非每个团队都有独立的个人在 100% 的时间内执行这些角色。有时,开发人员充当分析师,或者分析师充当测试人员。理想情况下,客户与执行分析师角色的团队坐在一起。重要的一点是这些角色应该始终存在于团队中。

Not every team has separate individuals who perform these roles 100% of the time. Sometimes, developers act as analysts, or analysts act as testers. Ideally, the customer is sitting with the team performing the analyst role. The important point is that these roles should always exist on the team.

迭代项目分析

Analysis on Iterative Projects

总的来说,在本书中,我们试图避免对您正在使用的开发过程进行任何预设。我们相信我们描述的模式对任何交付团队都有好处,无论他们使用什么流程。但是,我们相信迭代开发过程对于创建高质量软件至关重要。因此,如果我们在这里提供更多迭代开发过程的细节似乎与我们相关,我们希望您能原谅我们,因为它有助于描述分析师、测试人员和开发人员的角色。

In general, in this book we have attempted to avoid any presupposition of the development process that you are using. We believe that the patterns we describe are of benefit to any delivery team, whatever process they use. However, we believe that iterative development processes are essential to the creation of quality software. So, we hope that you will forgive us if it seems relevant to us to give a little more detail of iterative development processes here, because it helps to delineate the roles of the analyst, tester, and developer.

在迭代交付方法中,分析师花费大量时间来定义验收标准。这些是团队判断是否满足特定要求的标准。最初,分析师将与测试人员和客户密切合作来定义验收标准。鼓励分析师和测试人员在此阶段进行协作有助于双方并使流程更加有效。分析师获益是因为测试人员可以提供他们的经验,了解哪些事情可以并且应该被有效地衡量,以定义故事何时完成。在测试这些需求成为他们的主要关注点之前,测试人员通过了解需求的性质而受益。

In iterative approaches to delivery, analysts spend much of their time defining acceptance criteria. These are the criteria by which teams can judge if a particular requirement has been met. Initially, the analysts will work closely with testers and the customer to define acceptance criteria. Encouraging analysts and testers to collaborate at this stage helps both parties and makes for a more effective process. The analyst gains because the tester can provide the benefit of their experience of what sorts of things can and should be usefully measured to define when a story is done. The tester benefits by gaining an understanding of the nature of the requirements before the testing of those requirements becomes their primary focus.

一旦定义了验收标准,就在实施需求之前,分析师和测试人员与将执行实施的开发人员坐在一起,如果有的话,还有客户。分析师描述需求及其存在的业务环境,并通过验收标准。然后,测试人员与开发人员合作,就一系列自动验收测试达成一致,这些测试将证明验收标准已得到满足。

Once the acceptance criteria have been defined, just before the requirement is to be implemented, the analyst and tester sit with the developers who will do the implementation, along with the customer if available. The analyst describes the requirement and the business context in which it exists, and goes through the acceptance criteria. The tester then works with the developers to agree on a collection of automated acceptance tests that will prove that the acceptance criteria have been met.

这些简短的启动会议是将迭代交付过程结合在一起的粘合剂的重要组成部分,可确保实施需求的每一方都对该需求及其在交付过程中的作用有很好的理解。这种方法可以防止分析师创建实施或测试成本高昂的“象牙塔”要求。它可以防止测试人员提出不是缺陷而是对系统的误解的缺陷。它阻止开发人员实现与任何人真正想要的东西关系不大的东西。

These short kick-off meetings are a vital part of the glue that binds the iterative delivery process together, ensuring that every party to the implementation of a requirement has a good understanding of that requirement and of their role in its delivery. This approach prevents analysts from creating “ivory tower” requirements that are expensive to implement or test. It prevents testers from raising defects that aren’t defects but are instead a misunderstanding of the system. It prevents developers from implementing something that bears little relationship to what anyone really wants.

在实现需求时,如果开发人员发现他们不太了解的领域,或者如果他们发现问题或解决需求提出的问题的更有效方法,他们将咨询分析师。这种交互性是迭代交付过程的核心,部署管道提供的能力极大地促进了迭代交付过程,无论何时我们需要在我们选择的环境中运行我们的应用程序。

While the requirement is being implemented, the developers will consult with the analyst if they find an area that they don’t understand well enough, or if they have discovered a problem or a more efficient approach to solving the problem that the requirement poses. This interactivity is at the heart of iterative delivery processes that are enormously facilitated by the ability provided by the deployment pipeline to run our application whenever we need to on the environment of our choice.

当开发人员认为他们已经完成了工作——这意味着所有相关的单元和组件测试都通过了,并且验收测试已经全部实施并表明系统已经满足要求——他们将向分析师展示它,测试人员和客户。此审查允许分析师和客户查看需求的工作解决方案,并让他们有机会确认它确实按预期满足了需求。通常在这次审查中会挑出一些小问题向上,立即解决。有时,此类审查会引发对备选方案或变更影响的讨论。这是团队测试他们对系统发展方向的共同理解的好机会。

When the developers believe that they have completed the work—which means all of the associated unit and component tests pass, and the acceptance tests have all been implemented and show that the system has fulfilled the requirement—they will demonstrate it to the analyst, the tester, and the customer. This review allows the analyst and customer to see the working solution to the requirement, and gives them an opportunity to confirm that it does indeed fulfill the requirement as intended. Often at this review a few small issues will be picked up, which are addressed immediately. Sometimes, such reviews trigger a discussion of alternatives or the implications of the change. This is a good opportunity for the team to test their shared understanding of the direction in which the system is evolving.

一旦分析师和客户对满足要求感到满意,就会继续由测试人员进行测试。

Once the analyst and the customer are happy that the requirement has been fulfilled, it moves on to testing by the testers.

作为可执行规范的验收标准

Acceptance Criteria as Executable Specifications

随着自动化测试在使用迭代过程的项目交付中变得越来越重要,许多从业者已经意识到自动化测试不仅仅是测试。相反,验收测试是正在开发的软件行为的可执行规范。这是一个重要的认识,它催生了一种新的自动化测试方法,称为行为驱动开发。行为驱动开发的核心思想之一是您的验收标准应该以客户对应用程序行为的期望的形式编写。然后应该可以采用如此编写的验收标准,并直接针对应用程序执行它们以验证应用程序是否符合其规范。

As automated testing has become more central to the delivery of projects that use iterative processes, many practitioners have realized that automated testing is not just about testing. Rather, acceptance tests are executable specifications of the behavior of the software being developed. This is a significant realization which has spawned a new approach to automated testing, known as behaviordriven development. One of the core ideas of behavior-driven development is that your acceptance criteria should be written in the form of the customer’s expectations of the behavior of the application. It should then be possible to take acceptance criteria thus written, and execute them directly against the application to verify that the application meets its specifications.

这种方法有一些显着的优点。随着应用程序的发展,大多数规范开始变得过时。这对于可执行规范来说是不可能的:如果它们没有准确指定应用程序做什么,它们将在运行时针对该效果引发异常。当针对不符合其规范的应用程序版本运行时,管道的验收测试阶段将失败,因此该版本将不可用于部署或发布。

This approach has some significant advantages. Most specifications begin to become out-of-date as the application evolves. This is not possible for executable specifications: If they don’t specify what the application does accurately, they will raise an exception to that effect when run. The acceptance test stage of the pipeline will fail when run against a version of the application that does not meet its specifications, and that version will therefore not be available for deployment or release.

验收测试是面向业务的,这意味着它们应该验证您的应用程序是否为其用户带来了价值。分析师定义故事的验收标准——故事必须满足的标准才能被认为已经完成。Chris Matts 和 Dan North 提出了一种用于编写验收标准的特定领域语言,其形式如下:

Acceptance tests are business-facing, which means they should verify that your application delivers value to its users. Analysts define acceptance criteria for stories—criteria that must be fulfilled for the story to be recognized as done. Chris Matts and Dan North came up with a domain-specific language for writing acceptance criteria, which takes the following form:

给定一些初始上下文,

Given some initial context,

一个事件发生时,

When an event occurs,

然后有一些结果。

Then there are some outcomes.

就您的应用程序而言,“给定”代表您的应用程序在测试用例开始时的状态。“when”子句描述了用户和您的应用程序之间的交互。“then”子句描述了此交互完成后应用程序的状态。测试用例的工作是使应用程序进入“给定”子句中描述的状态,执行“when”子句中描述的操作,并验证应用程序的状态是否与“then”子句中描述的一样。

In terms of your application, “given” represents the state of your application at the beginning of the test case. The “when” clause describes an interaction between a user and your application. The “then” clause describes the state of the application after this interaction has completed. The job of your test case is to get the application into the state described in the “given” clause, perform the actions described in the “when” clause, and verify that the application’s state is as described in the “then” clause.

例如,考虑一个金融交易应用程序。我们可以按照以下格式编写验收标准:

For example, consider a financial trading application. We can write an acceptance criterion in the following format:

图片

Cucumber、JBehave、Concordion、Twist 和 FitNesse 等工具允许您将这些验收标准编写为纯文本,并使它们与实际应用程序保持同步。例如,在 Cucumber 中,您可以将上述验收标准保存在名为 features/placing_an_order.feature 的文件中。此文件表示图 8.2中的验收标准。然后,您将创建一个 Ruby 文件,列出此场景所需的步骤,如 features/step_definitions/placing_an_order_steps.rb。该文件表示图 8.2中的测试实现层。

Tools like Cucumber, JBehave, Concordion, Twist, and FitNesse allow you to write acceptance criteria like these as plain text and keep them synchronized with the actual application. For example, in Cucumber, you would save the acceptance criterion described above in a file called something like features/placing_an_order.feature. This file represents the acceptance criteria in Figure 8.2. You would then create a Ruby file, listing the steps required for this scenario, as features/step_definitions/placing_an_order_steps.rb. This file represents the test implementation layer in Figure 8.2.

图片

要支持此测试和其他测试,您需要在目录application_driver中创建AdminApiTradingUi这些类构成了图 8.2中应用程序驱动层的一部分。如果您的应用程序是基于 Web 的,他们可能会调用 Selenium、Sahi 或 WebDriver;如果它是富客户端 .NET 应用程序,他们可能会调用 White;如果您的应用程序具有 REST API,他们可能会调用 HTTP POST 或 GET。在命令行上运行cucumber会提供以下输出:

To support this and other tests, you would need to create the AdminApi and TradingUi classes in the directory application_driver. These classes form part of the application driver layer in Figure 8.2. They might call Selenium, Sahi, or WebDriver, if your application is web based, or White if it’s a rich client .NET application, or use HTTP POST or GET if your application has a REST API. Running cucumber on the command line delivers the following output:

图片

这种创建可执行规范的方法是行为驱动设计的本质。回顾一下,这是一个过程:

This approach to creating executable specifications is the essence of behavior-driven design. To recap, this is the process:

• 与客户讨论您的故事的验收标准。

• Discuss acceptance criteria for your story with your customer.

• 以上述可执行格式将它们写下来。

• Write them down in the executable format described above.

• 编写一个只使用领域语言的测试实现,访问应用程序驱动层。

• Write an implementation for the test which uses only the domain language, accessing the application driver layer.

• 创建一个与被测系统对话的应用程序驱动层。

• Create an application driver layer which talks to the system under test.

使用这种方法代表了在 Word 文档或跟踪工具中保留验收标准并使用记录和回放创建验收测试的传统方法的重大进步。可执行规范形成了测试记录系统——它们确实是可执行规范。不再需要测试人员和分析师编写被抛出的 Word 文档越过墙面向开发人员——分析师、客户、测试人员和开发人员可以在开发过程中就可执行规范进行协作。

Using this approach represents a significant advance over the traditional method of keeping acceptance criteria in Word documents or tracking tools and using record-and-playback to create acceptance tests. The executable specifications form the system of record for tests—they really are executable specifications. There is no more need for testers and analysts to write Word documents that are thrown over the wall to developers—analysts, customers, testers, and developers can collaborate on executable specifications during the development process.

对于从事具有特定法规约束的项目的读者来说,值得注意的是,这些可执行规范通常可以使用简单的自动化过程转化为适合审计的文档。我们在几个团队中成功完成了这项工作,审计员对结果非常满意。

For readers working on projects with particular regulatory constraints, it is worth noting that these executable specifications can generally be turned into a document suitable for auditing using a simple, automated process. We have worked in several teams where this was done successfully and the auditors were very happy with the results.

应用驱动层

The Application Driver Layer

应用程序驱动层是了解如何与您的应用程序(被测系统)通信的层。应用程序驱动层的 API 是用领域语言表达的,确实可以将其视为一种领域特定的语言。

The application driver layer is the layer that understands how to talk to your application—the system under test. The API for the application driver layer is expressed in a domain language, and indeed can be thought of as a domain-specific language in its own right.

有了设计良好的应用程序驱动层,就可以完全省去验收标准层,并在测试的实施中表达验收标准。这是我们在上面的 Cucumber,表示为一个简单的 JUnit 测试。这个例子是从戴夫目前的项目中改编而来的。

With a well-designed application driver layer, it becomes possible to completely dispense with the acceptance criteria layer and express the acceptance criteria in the implementation of the test. Here is the same acceptance test we wrote in Cucumber above, expressed as a simple JUnit test. This example is very lightly adapted from Dave’s current project.

图片

此测试创建一个新用户,成功注册他们并确保他们有足够的资金进行交易。它还为他们创造了一种新的交易工具。这两项活动本身都是复杂的交互,但 DSL 将它们抽象到一定程度,使得初始化此测试的任务像几行代码一样简单。以这种方式编写的测试的关键特征是它们从测试的实现细节中抽象出测试。

This test creates a new user, registering them successfully and ensuring that they have sufficient funds to trade. It also creates a new instrument for them to trade upon. Both of these activities are complex interactions in their own right, but the DSL abstracts them to a degree that makes the task of initializing this test as simple as a couple of lines of code. The key characteristics of tests written in this way is that they abstract the tests from the details of their implementation.

这些测试的一个关键特征是使用别名来表示键值。在上面的示例中,我们创建了一个名为bond的工具和一个名为Dave的系统用户。应用程序驱动程序在幕后所做的是创建真实的工具和用户,每个工具和用户都有其自己由应用程序生成的唯一标识符。应用程序驱动程序将在内部为这些值设置别名,以便我们始终可以引用Davebond,即使真实用户可能被称为testUser11778264441 之类的名称。该值是随机的,每次运行测试时都会更改,因为每次都会创建一个新用户。

One key characteristic of these tests is the use of aliases to represent key values. In the example above we create an instrument named bond and a user of the system called Dave. What the application driver does behind the scenes is create real instruments and users, each with its own unique identifier generated by the application. The application driver will alias these values internally so that we can always refer to Dave or bond, even though the real user is probably called something like testUser11778264441. That value is randomized and will change every time the test is run because a new user is created each time.

这有两个好处。首先,它使验收测试完全相互独立。因此,您可以轻松地并行运行验收测试,而不必担心它们会影响彼此的数据。其次,它允许您使用一些简单的高级命令创建测试数据,使您无需为测试集合维护复杂的种子数据。

This has two benefits. First, it makes acceptance tests completely independent of each other. Thus you can easily run acceptance tests in parallel without worrying that they will step on each other’s data. Second, it allows you to create test data with a few simple high-level commands, freeing you from the need to maintain complex seed data for collections of tests.

在上面显示的 DSL 样式中,每个操作(placeOrderconfirmOrderSuccess等)都使用多个字符串参数定义。一些参数是必需的,但大多数是可选的,具有简单的默认值。例如,登录操作允许我们指定,除了用户的别名,一个特定的密码和一个产品代码。如果我们的测试不关心这些细节,DSL 将提供有效的默认值。

In the style of DSL shown above, each operation (placeOrder, confirmOrderSuccess, and so on) is defined with multiple string parameters. Some parameters are required, but most are optional with simple defaults. For example, the login operation allows us to specify, in addition to the alias for a user, a specific password and a product code. If our test doesn’t care about these details, the DSL will supply defaults that work.

为了让您了解此处发生的违约程度,我们的createUser指令的完整参数集是:

To give you an indication of the level of defaulting going on here, the full set of parameters for our createUser instruction are:

姓名(必填)

name (required)

密码(默认密码

password (defaults to password)

产品类型(默认为DEMO

productType (defaults to DEMO)

余额(默认为15000.00

balance (defaults to 15000.00)

货币(默认为美元

currency (defaults to USD)

fxRate(默认为1

fxRate (defaults to 1)

名字(默认为名字

firstName (defaults to Firstname)

姓氏(默认为姓氏

lastName (defaults to Surname)

电子邮件地址(默认为test@somemail.com

emailAddress (defaults to test@somemail.com)

•家庭电话(默认为02012345678

homeTelephone (defaults to 02012345678)

securityQuestion1(默认为最喜欢的颜色?

securityQuestion1 (defaults to Favourite Colour?)

securityAnswer1(默认为蓝色

securityAnswer1 (defaults to Blue)

设计良好的应用程序驱动层的结果之一是提高了测试可靠性。该示例所采用的系统实际上是高度异步的,这意味着我们的测试通常必须等待结果才能进入下一步。这可能导致间歇性或脆弱的测试,这些测试对事物时间的微小变化很敏感。由于使用 DSL 时隐含的高度重用性,复杂的交互和操作可以一次编写并用于多次测试。如果在作为验收测试套件的一部分运行测试时出现间歇性问题,它们将在一个地方修复,以确保未来重用这些功能的测试同样可靠。

One of the consequences of a well-designed application driver layer is improved test reliability. The system this example is taken from is in reality highly asynchronous, meaning that our tests often have to wait for results before progressing to the next step. This can lead to intermittent or fragile tests that are sensitive to slight changes in the timing of things. Because of the high degree of reuse that is implicit in the use of a DSL, complex interactions and operations can be written once and used in many tests. If intermittent problems appear when the tests are run as part of your acceptance test suite, they will be fixed in a single place, that ensuring future tests that reuse these features will be equally reliable.

我们非常简单地开始构建应用程序驱动层——通过建立一些案例并构建一些简单的测试。从那时起,团队根据需求进行工作,并在发现该层缺少特定测试所需的某些功能时添加到该层。在相对较短的时间内,应用程序驱动层及其 API 代表的 DSL 趋于变得相当广泛。

We start the construction of an application driver layer very simply—by establishing a few cases and building some simple tests. From then on, the team works on requirements and adds to the layer whenever they find that it is lacking some feature a particular test requires. Over a relatively short time, the application driver layer, along with the DSL represented by its API, tends to become quite extensive.

如何表达你的接受标准

How to Express Your Acceptance Criteria

将上面 JUnit 中的示例验收测试与上一节中用 Cucumber 表示的示例进行比较是很有启发意义的。这些方法中的任何一种都可以正常工作,并且每种方法都有其优点和缺点。这两种方法都代表了对传统验收测试方法的重大改进。Jez 在他当前的项目中使用了 Cucumber 风格的方法(尽管使用的是 Twist,而不是 Cucumber),而 Dave 则直接使用 JUnit(如上例)。

It is instructive to compare the example acceptance test in JUnit, above, to the one expressed in Cucumber in the previous section. Either of these approaches will work just fine, and each has its pros and cons. Both approaches represent a significant improvement over the traditional approaches to acceptance testing. Jez is using the Cucumber-style approach in his current project (although using Twist, rather than Cucumber), while Dave is using JUnit directly (like the example above).

外部 DSL 方法的好处是您可以往返接受标准。与其在你的跟踪工具中设置验收标准,然后在你的 xUnit 测试套件中重新表达它们,你的验收标准——以及你的故事——就是的可执行规范。然而,虽然现代工具减少了编写可执行的验收标准并使它们与验收测试实现同步的开销,但不可避免地存在一些开销。5个

The benefit of the external DSL approach is that you can round-trip your acceptance criteria. Instead of having acceptance criteria in your tracking tool and then reexpressing them in your xUnit test suite, your acceptance criteria—and your stories—simply are your executable specifications. However, while modern tools reduce the overhead of writing executable acceptance criteria and keeping them synchronized with the acceptance test implementation, there is inevitably some overhead.5

如果您的分析师和客户有足够的技术来处理使用内部 DSL 编写的 xUnit 测试,那么使用直接 xUnit 方法效果很好。它需要不太复杂的工具,并且您可以使用常规开发环境中内置的自动完成功能。您还可以从测试中直接访问 DSL,而不必通过一定程度的间接访问——如上所述,别名方法的所有功能都触手可及。然而,虽然您可以使用像 AgileDox 这样的工具将您的类和方法名称转换为纯文本文档,其中列出了功能(上例中的“下订单”)和场景(“用户订单应正确记入账户”),很难将实际测试转换为一组纯文本步骤。此外,

If your analysts and customers are sufficiently technical to work with xUnit tests written using the internal DSL, using the direct xUnit approach works great. It requires less complex tooling, and you can use the autocomplete functionality that is built into regular development environments. You also have direct access to the DSL from your tests instead of having to go through a level of indirection—with all the power of the aliasing approach, described above, at your fingertips. However, while you can use a tool like AgileDox to turn your class and method names into a plain text document that lists the features (“Placing an order” in the example above) and scenarios (“User order should debit account correctly”), it’s harder to convert the actual test into a set of plain text steps. Furthermore, the conversion is one-way—you have to make changes in the tests, not in the acceptance criteria.

窗口驱动程序模式:将测试与 GUI 解耦

The Window Driver Pattern: Decoupling the Tests from the GUI

本章中的示例旨在清楚地说明验收测试分为三层:可执行验收标准、测试实现和应用程序驱动层。应用程序驱动层是唯一了解如何与应用程序交互的层——其他两个层仅使用业务领域语言。如果您的应用程序有一个 GUI,并且您已经决定您的验收测试应该针对 GUI 运行,应用程序驱动程序层将了解如何与之交互。与 GUI 交互的应用程序驱动层部分称为窗口驱动程序。

The examples in this chapter are designed to clearly illustrate the separation of acceptance tests into three layers: executable acceptance criteria, test implementation, and the application driver layer. The application driver layer is the only layer which understands how to interact with the application—the two other layers use only the domain language of the business. If your application has a GUI, and you have decided that your acceptance tests should run against the GUI, the application driver layer will understand how to interact with this. The part of your application driver layer that interacts with the GUI is known as the window driver.

窗口驱动程序模式旨在通过提供一个抽象层来减少验收测试与被测系统的 GUI 之间的耦合,从而使针对 GUI 运行的测试不那么脆弱。因此,它有助于将我们的测试与系统 GUI 更改的影响隔离开来。本质上,我们编写了一个抽象层,伪装成我们测试的用户界面。所有测试仅通过这一层与真实 UI 交互。因此,如果对 GUI 进行了更改,我们可以对窗口驱动程序进行相应的更改,从而使窗口驱动程序的界面和测试保持不变。

The window driver pattern is designed to make tests that run against the GUI less brittle, by providing a layer of abstraction that reduces the coupling between the acceptance tests and the GUI of the system under test. It thus helps to insulate our tests from the effect of changes to the GUI of the system. In essence, we write an abstraction layer that pretends to be the user interface to our tests. All of the tests interact with the real UI solely via this layer. Thus if changes are made to the GUI, we can make corresponding changes to the window driver which leaves the window driver’s interface, and therefore the tests, unchanged.

FitNesse 开源测试工具采用了非常相似的方法,允许将 Fit 装置创建为“驱动程序”,无论您需要测试什么。在这种情况下,这是一个很好的工具。

The FitNesse open source testing tool takes a very similar approach, allowing Fit fixtures to be created as the “drivers” to whatever it is that you need to test. This is an excellent tool that comes into its own in this context.

图 8.3 窗口驱动模式在验收测试中的使用

Figure 8.3 The use of the window driver pattern in acceptance testing

图片

当实现窗口驱动程序模式时,你应该为你的 GUI 的每个部分编写一个设备驱动程序的等价物。验收测试代码仅通过适当的窗口驱动程序与 GUI 交互。窗口驱动程序提供了一个抽象层,它构成了您的应用程序驱动程序层的一部分,以将您的测试代码与 UI 细节的变化隔离开来。当 UI 更改时,您更改窗口驱动程序中的代码,并且所有依赖于它的测试都是固定的。窗口驱动程序模式如图 8.3所示。

When implementing the window driver pattern, you should write the equivalent of a device driver for each part of your GUI. Acceptance test code only interacts with the GUI through an appropriate window driver. The window driver provides a layer of abstraction, which forms part of your application driver layer, to insulate your test code from changes in the specifics of the UI. When the UI changes, you change the code in the window driver, and all of the tests that depend upon it are fixed. The window driver pattern is shown in Figure 8.3.

应用程序驱动程序和窗口驱动程序之间的区别是:它是了解如何与 GUI 交互的窗口驱动程序。如果您为您的应用程序提供了一个新的 GUI——例如,除了 Web 界面之外还有一个富客户端——您只需创建一个插入应用程序驱动程序的新窗口驱动程序。

The distinction between the application driver and the window driver is this: It is the window driver that understands how to interact with the GUI. If you provided a new GUI for your application—for example, a rich client in addition to the web interface—you would just create a new window driver plugged into the application driver.

下面是一个验收测试示例,没有使用本章中描述的任何分层:

Here is an example of an acceptance test written without any of the layering described in this chapter:

图片

下面是重构为两层的同一示例:测试实现和窗口驱动程序。本例中的AccountPanelDriver是窗口驱动程序。这是分解测试的良好开端。

Here is the same example refactored into two layers: test implementation and window driver. The AccountPanelDriver in this example is the window driver. This is a good start to decomposing your tests.

图片

我们可以看到测试语义和与作为其基础的 UI 交互的细节之间更加清晰的分离。如果你考虑代码支持此测试的窗口驱动程序中的代码,此测试中的代码总体上更多,但抽象级别更高。我们将能够在与页面交互的许多不同测试中重用窗口驱动程序,并在进行过程中对其进行增强。

We can see a much clearer separation between the semantics of the test and the details of interacting with the UI that underlies it. If you consider the code that underpins this test, the code in the window driver, there is more code overall in this test, but the level of abstraction is higher. We will be able to reuse the window driver across many different tests that interact with the page, enhancing it as we go.

如果为了我们的示例,企业决定不使用基于 Web 的用户界面,我们的产品在触摸屏上使用基于手势的用户界面会更有效,那么该测试的基本原理将保持不变。我们可以创建一个新的窗口驱动程序来与基于手势的 UI 进行交互,而不是与无聊的旧网页进行交互,将其替换为应用程序驱动程序层内的原始驱动程序,测试将继续进行。

If, for the sake of our example, the business decided that instead of a web-based user interface, our product would be more effective with a gesture-based user interface on a touch screen, the fundamentals of this test would remain the same. We could create a new window driver to interact with the gesture-based UI instead of the boring old web page, substitute it with the original driver inside the application driver layer, and the test would continue to work.

实施验收测试

Implementing Acceptance Tests

验收测试的实施不仅仅是分层。验收测试涉及将应用程序置于特定状态,对其执行多项操作,然后验证结果。必须编写验收测试来处理异步和超时以避免不稳定。必须谨慎管理测试数据。为了模拟与外部系统的任何集成,通常需要测试替身。这些主题是本节的主题。

There is more to the implementation of acceptance tests than layering. Acceptance tests involve putting the application in a particular state, performing several actions on it, and verifying the results. Acceptance tests must be written to handle asynchrony and timeouts in order to avoid flakiness. Test data must be managed carefully. Test doubles are often required in order to allow any integration with external systems to be simulated. These topics are the subject of this section.

验收测试中的状态

State in Acceptance Tests

在前一章中,我们讨论了有状态单元测试的问题,并提供了尝试尽量减少测试对状态的依赖的建议。这对于验收测试来说是一个更为复杂的问题。验收测试旨在模拟用户与系统的交互方式,以执行它并证明它满足其业务需求。当用户与您的系统交互时,他们将建立并依赖您的系统管理的信息。没有这样的状态,您的验收测试就没有意义。但是建立一个已知良好的起始状态,这是任何实际测试的先决条件,然后构建一个依赖于该状态的测试可能很困难。

In the preceding chapter we discussed the problems of stateful unit tests and offered advice to try and minimize your tests’ reliance on state. This is an even more complex problem for acceptance testing. Acceptance tests are intended to simulate user interactions with the system in a manner that will exercise it and prove that it meets its business requirements. When users interact with your system, they will be building up, and relying upon, the information that your system manages. Without such state your acceptance tests are meaningless. But establishing a known-good starting state, the prerequisite of any real test, and then building a test to rely on that state can be difficult.

当我们谈到状态测试时,我们使用的是一种速记。我们使用术语“有状态”的意思是为了测试应用程序的某些行为,测试取决于应用程序处于特定的起始状态(行为驱动开发的“给定”子句) . 也许该应用程序需要一个具有特定权限的帐户,或者一个特定的库存项目集合来进行操作。无论所需的起始状态如何,让应用程序准备好展示被测行为通常是编写测试中最困难的部分。

When we speak of stateful tests we are using a bit of a shorthand. What we mean to imply by the use of the term “stateful” is that in order to test some behavior of the application, the test depends upon the application being in a specific starting state (the “given” clauses of behavior-driven development). Perhaps the application needs an account with specific privileges, or a particular collection of stock items to operate against. Whatever the required starting state, getting the application ready to exhibit the behavior under test is often the most difficult part of writing the test.

虽然我们不能现实地从任何测试中消除状态,更不用说验收测试了,但重要的是将重点放在最小化测试对复杂状态的依赖性上。

Although we can’t realistically eliminate state from any test, let alone an acceptance test, it is important to focus on minimizing the test’s dependency on a complex state.

首先,避免获取生产数据转储来填充测试数据库以进行验收测试的诱惑(尽管这有时可能是对容量测试很有用)。相反,保持一个受控的、最小的集合。测试的一个关键方面是建立一个已知良好的起点。如果您尝试在测试环境中跟踪生产系统的状态——我们在各种组织中多次看到这种方法——您将花费更多的时间来尝试使数据集正常工作而不是进行测试。毕竟,测试的重点应该是系统的行为,而不是数据。

First of all, avoid the lure of obtaining a dump of production data to populate your test database for your acceptance tests (although this can occasionally be useful for capacity testing). Instead, maintain a controlled, minimal set. A key aspect of testing is to establish a known-good starting point. If you attempt to track the state of a production system in your test environment—an approach we have seen many times in a variety of organizations—you will spend more time trying to get the dataset working than you will testing. After all, the focus of your testing should be the behavior of the system, not the data.

维护允许您探索系统行为的最小连贯数据集。自然地,这个最小的起始状态应该表示为一组脚本,存储在您的版本控制系统中,可以在您的验收测试运行开始时应用。理想情况下,正如我们在第 12 章“管理数据”中所述,测试应该使用应用程序的公共 API 将其置于正确的状态以开始测试。这比将数据直接运行到应用程序的数据库更安全。

Maintain the minimum coherent set of data that allows you to explore the system’s behavior. Naturally, this minimal starting state should be represented as a collection of scripts, stored in your version control system, that can be applied at the commencement of your acceptance test run. Ideally, as we describe in Chapter 12, “Managing Data,” the tests should use your application’s public API to put it in the correct state to begin tests. This is less brittle than running data directly into the application’s database.

理想的测试应该是原子的。拥有原子测试意味着它们执行的顺序无关紧要,从而消除了难以跟踪错误的主要原因。这也意味着测试可以并行运行,这对于在您拥有任何规模的应用程序后获得快速反馈至关重要。

The ideal test should be atomic. Having atomic tests means that the order in which they execute does not matter, eliminating a major cause of hard-to-track bugs. It also means that the tests can be run in parallel, which is essential to getting fast feedback once you have an application of any size.

原子测试创建它需要执行的所有内容,然后在其自身后面进行整理,除了它是通过还是失败的记录之外不留下任何痕迹。这在验收测试中可能很难实现,但并非不可能。在处理事务系统(尤其是关系数据库)时,我们经常用于组件测试的一种技术是在测试开始时建立一个事务,然后在测试结束时将其回滚。因此,数据库保持测试运行前的相同状态。不幸的是,如果您采纳我们的另一条建议,即将验收测试视为端到端测试,那么您通常无法使用这种方法。

An atomic test creates all it needs to execute and then tidies up behind itself, leaving no trace except a record of whether it passed or failed. This can be difficult, though not impossible, to achieve in acceptance tests. One technique that we use regularly for component tests when dealing with transactional systems, particularly relational databases, is to establish a transaction at the beginning of a test, and then roll it back at the conclusion of the test. Thus the database is left in the same state it was before the test ran. Unfortunately, if you take another of our pieces of advice, which is to treat acceptance tests as end-to-end tests, this approach isn’t usually available to you.

最有效的验收测试方法是使用应用程序的功能来隔离测试范围。例如,如果您的软件支持拥有独立帐户的多个用户,请使用您的应用程序的功能在每次测试开始时创建一个新帐户,如上一节中的示例所示。在您的应用程序驱动层中创建一些简单的测试基础结构,使新帐户的创建变得非常简单。现在,当您的测试运行时,属于与测试关联的账户的任何活动和结果状态都独立于其他账户中发生的活动。这种方法不仅确保您的测试是隔离的,而且还测试了这种隔离,特别是当您并行运行验收测试时。

The most effective approach to acceptance testing is to use the features of your application to isolate the scope of the tests. For example, if your software supports multiple users who have independent accounts, use the features of your application to create a new account at the start of every test, as shown in the example in the previous section. Create some simple test infrastructure in your application driver layer to make the creation of new accounts trivially simple. Now when your test is run, any activities and resulting state belonging to the account associated with the test is independent of activities taking place in other accounts. Not only does this approach ensure that your tests are isolated, but it also tests that isolation, particularly when you run acceptance tests in parallel. This effective approach is only problematic if the application is unusual enough to have no natural means of isolating cases.

但是,有时除了在测试用例之间共享状态之外别无选择。在这些情况下,确实必须非常仔细地设计测试。像这样的测试往往是脆弱的,因为它们不是在起点已知的环境中运行。简单地说,如果您编写的测试将四条记录写入数据库,然后为下一步检索第三条记录,那么您最好确定在您的测试之前没有其他人添加任何行开始,否则你会选择错误的记录。您还应该小心,不要在运行之间没有执行拆卸过程的情况下重复运行测试。这些是维护和保持运行的讨厌测试。遗憾的是,它们有时很难避免,但尽可能避免它们是值得的。仔细考虑如何以不同的方式设计测试,以便它不会留下任何状态。

Sometimes, though, there is no alternative to sharing state between test cases. In these circumstances, tests must be designed very carefully indeed. Tests like these have a tendency to be fragile because they are not operating in an environment where the starting point is known. Simplistically, if you write a test that writes four records to the database and then retrieves the third for the next step, you had better be certain that no one else added any rows before your test started, or you will pick the wrong record. You also should be careful that you don’t run your tests repeatedly without a tear-down process being executed between runs. These are nasty tests to maintain and keep running. Sadly, they are sometimes hard to avoid, but it is worth the effort to try to avoid them as much as you can. Think carefully about how to design the test differently so that it won’t leave any state behind.

当你到了最后的手段,发现你必须创建开始状态无法保证且无法清理的测试时,我们建议你将重点放在使此类测试具有很强的防御性。验证测试开始时的状态是否符合您的预期,如果有任何异常,请立即使测试失败。使用前提断言保护您的测试,确保系统已准备好运行您的测试。使此类测试以相对而非绝对的方式起作用;例如,不要编写将三个对象添加到集合中然后确认其中只有三个对象的测试,而是获取初始计数并断言有x + 3。

When you reach the last resort and find that you must create tests whose starting state cannot be guaranteed and that cannot be cleaned up, we recommend that you focus on making such tests very defensive. Verify that the state at the beginning of the test is what you expect, and fail the test immediately if anything seems untoward. Protect your test with precondition assertions that ensure that the system is ready to run your test. Make such tests work in relative rather than absolute terms; for example, don’t write a test that adds three objects to a collection and then confirms that there are only three objects in it, but instead get an initial count and assert that there are x + 3.

流程边界、封装和测试

Process Boundaries, Encapsulation, and Testing

最直接的测试,因此应该成为所有验收测试模型的测试,是那些无需任何特权访问即可证明系统需求的测试。自动化测试的新手认识到,要使他们的代码可测试,他们将不得不修改其设计方法,这是事实。但通常,他们期望他们需要为他们的代码提供许多秘密后门以确认结果,这是不正确的。正如我们在其他地方所描述的,自动化测试确实会给您带来压力,让您的代码更加模块化和更好地封装,但是如果您打破封装以使其可测试,您通常会错过实现相同目标的更好方法。

The most straightforward tests, and therefore the tests that should be your model for all acceptance tests, are those that prove requirements of the system without the need for any privileged access to it. Newcomers to automated testing recognize that to make their code testable they will have to modify their approach to its design, which is true. But often, they expect that they will need to provide many secret back doors to their code to allow results to be confirmed, which is not true. As we have described elsewhere, automated testing does apply a pressure on you to make your code more modular and better encapsulated, but if you are breaking encapsulation to make it testable you are usually missing a better way to achieve the same goal.

在大多数情况下,您应该对待创建一段代码的愿望,该代码仅存在于允许您以极大的怀疑验证应用程序的行为。努力避免这种特权访问,在你让步之前认真思考,对自己采取强硬路线——在你完全确定你找不到更好的方法之前不要屈服于简单的选择。

In most cases, you should treat a desire to create a piece of code that only exists to allow you to verify the behavior of the application with a great deal of suspicion. Work hard to avoid such privileged access, think hard before you relent, take a hard line with yourself—and don’t give in to the easy option until you are absolutely certain that you can’t find a better way.

然而,有时灵感会失败,你不得不提供某种后门。这些可能是允许您修改系统某些部分的行为的调用,可能返回一些关键结果,或者将系统的那部分切换到特定的测试模式。如果您真的别无选择,则此方法有效。但是,我们建议您只对系统外部的组件执行此操作,将负责与外部组件交互的代码替换为可控存根或其他一些测试替身。我们建议您永远不要将仅测试接口添加到将部署到生产中的远程系统组件。

However, sometimes inspiration fails and you are forced to provide a back door of some kind. These may be the calls allowing you to modify the behavior of some part of the system, perhaps to return some key results, or to switch that part of the system into a specific test mode. This approach works if you really have no other choice. However, we would advise you to only do this for components that are external to your system, replacing the code responsible for interacting with the external component with a controllable stub or some other test double. We would recommend that you never add test-only interfaces to remote system components that will be deployed into production.

作为特殊接口的替代方案,您可以提供对“魔法”数据值做出反应的测试时间组件。同样,此策略有效,但应保留给不会作为生产系统的一部分部署的组件。这是测试替身的有用策略。

As an alternative to special interfaces, you can provide test-time components that react to “magic” data values. Again, this strategy works, but should be reserved for components that will not be deployed as part of your production system. This is a useful strategy for test doubles.

这两种策略都倾向于导致经常需要修补的高维护测试。真正的解决方案是尽可能避免此类妥协,并依靠系统本身的实际行为来验证任何测试是否成功完成。仅在您用完其他选项时才保存这些策略。

Both of these strategies tend to result in high-maintenance tests that frequently need tinkering with. The real solution is to try and avoid these kinds of compromises wherever you can and rely on the actual behavior of the system itself to verify successful completion of any test. Save these strategies only for when you run out of other options.

管理异步和超时

Managing Asynchrony and Timeouts

测试异步系统会带来一系列问题。对于单元测试,您应该避免在测试范围内或实际上跨越测试边界的任何异步。后一种情况会导致难以发现的间歇性测试失败。对于验收测试,根据应用程序的性质,异步可能无法避免。这个问题不仅会出现在显式异步系统中,还会出现在任何使用线程或事务的系统中。在这样的系统中,您进行的调用可能需要等待另一个线程或事务完成。

Testing asynchronous systems presents its own collection of problems. For unit tests, you should avoid any asynchrony within the scope of a test, or indeed across the boundaries of tests. The latter case can cause hard-to-find intermittent test failures. For acceptance testing, depending on the nature of your application, asynchrony may be impossible to avoid. This problem can occur not just with explicitly asynchronous systems but also with any system that uses threads or transactions. In such systems, the call you make may need to wait for another thread or transaction to complete.

这里的问题归结为:测试失败了,还是我们只是在等待结果的到来?我们发现最有效的策略是构建将测试本身与此问题隔离开来的固定装置。技巧是,就测试本身而言,使体现测试的事件序列看起来是同步的。这是通过隔离同步调用背后的异步来实现的。

The problem here boils down to this: Has the test failed, or are we just waiting for the results to arrive? We have found that the most effective strategy is to build fixtures that isolate the test itself from this problem. The trick is, as far as the test itself is concerned, to make the sequence of events embodying the test appear to be synchronous. This is achieved by isolating the asynchrony behind synchronous calls.

想象一下,我们正在构建一个收集文件并存储它们的系统。我们的系统将有一个收件箱,一个文件系统上的位置,它会定期轮询。当它在那里找到一个文件时,它会安全地存储它,然后向某人发送一封电子邮件,说有一个新文件已经到达。

Imagine a that we are building a system that collects files and stores them. Our system will have an inbox, a location on a filesystem, which it will poll at regular intervals. When it finds a file there, it will store it safely and then send an email to someone to say that a new file has arrived.

当我们编写要在提交时运行的单元测试时,我们可以单独测试系统的每个组件,并断言每个组件都使用测试替身在这个小对象集群中与其邻居适当地交互。此类测试实际上不会触及文件系统,但会使用测试替身来模拟文件系统。如果我们在测试过程中遇到时间作为一个概念——我们会因为轮询——我们将伪造时钟,或者只是强制轮询为“现在”。

When we are writing unit tests to be run at commit time, we can test each component of our system in isolation, asserting that each interacts with its neighbors appropriately in this little cluster of objects using test doubles. Such tests will not actually touch the filesystem, but will use a test double to simulate the filesystem. If we run into time as a concept during the course of our testing—which we will because of the polling—we will fake the clock, or just force the poll to be “now.”

对于我们的验收测试,我们需要了解更多。我们需要知道我们的部署已经有效地工作,我们已经能够配置轮询机制,我们的电子邮件服务器已正确配置,并且我们所有的代码都可以无缝地协同工作。

For our acceptance test, we need to know more. We need to know that our deployment has worked effectively, that we have been able to configure the polling mechanism, that our email server is correctly configured, and that all of our code works seamlessly together.

我们这里的测试有两个问题:系统在检查新文件是否到达之前等待的轮询间隔,以及电子邮件到达所需的时间长度。

There are two problems for our test here: the polling interval that the system waits for before checking to see if a new file has arrived, and the length of time it takes for an email to arrive.

我们理想测试的大纲(使用 C# 语法)看起来像这样:

The outline of our ideal test (using C# syntax) would look something like this:

图片

然而,如果我们天真地编写这个测试的代码,当我们到达测试中的那一行时,简单地检查我们是否收到了我们期望的电子邮件,我们的测试几乎肯定会超过应用程序。当我们检查它是否到达时,还没有收到电子邮件。我们的测试将失败,尽管实际上它比我们的应用程序发送电子邮件更快地获得断言。

However, if we write the code of this test naively, simply checking that we have the email we expect when we get to that line in the test, our test will almost certainly outpace the application. The email won’t have been received by the time we check for its arrival. Our test will fail, although actually it was just quicker at getting to the assertion than our application was at delivering the email.

图片

相反,我们的测试必须暂停,让应用程序有机会在决定失败之前赶上来。

Instead, our test must pause, giving the application an opportunity to catch up before deciding on failure.

图片

如果我们使DELAY_PERIOD足够长,这将作为有效测试。

If we make the DELAY_PERIOD long enough, this will work as a valid test.

这种方法的缺点是这些DELAY_PERIOD 会很快累加起来。我们曾经将验收测试的时间从 2 小时减少到 40 分钟,方法是从这种策略更改为稍微调整一些的策略。

The drawback with this approach is that these DELAY_PERIODs quickly add up. We once reduced the time of our acceptance tests from 2 hours to 40 minutes by changing from this strategy to something a little more tuned.

新战略主要基于两个想法。一个是轮询结果,另一个是监视中间事件作为测试的入口。我们不是简单地等待超时前最长的可接受时间,而是实施了一些重试。

The new strategy was based, principally, on two ideas. One was to poll for results, and the other was to monitor intermediate events as a gate to the test. Instead of simply waiting for the longest acceptable period before timing out, we implemented some retries.

图片

在此示例中,我们保留了一个小暂停,否则我们会浪费宝贵的 CPU 周期来检查本可以用于处理传入电子邮件的电子邮件。但即使使用此SMALL_PAUSE,此测试也比之前的版本更有效,前提是SMALL_PAUSE与DELAY_PERIOD相比较小(通常小两个或更多个数量级)。

In this example, we have retained a small pause, because otherwise we waste valuable CPU cycles checking for the email that could have been spent processing the incoming email. But even with this SMALL_PAUSE, this test is much more efficient than the preceding version, providing that SMALL_PAUSE is small compared to DELAY_PERIOD (typically two or more orders of magnitude smaller).

最后的增强有点机会主义,并且在很大程度上取决于您的应用程序的性质。我们发现,在大量使用异步的系统中,通常还有其他事情可以提供帮助。在我们的例子中,想象一下我们有一个处理传入电子邮件的服务。当一封电子邮件到达时,它会为此生成一个事件。如果我们等待该事件而不是轮询电子邮件的到达,我们的测试会变得更快(如果更复杂)。

The final enhancement is a little more opportunistic, and will depend very much on the nature of your application. We have found that in systems that use asynchrony a lot, there are usually other things going on that can help. In our example, imagine for a moment that we have a service that handles incoming emails. When an email arrives, it generates an event to that effect. Our test becomes quicker (if more complex) if we wait for that event instead of polling for the arrival of the email.

图片

就ConfirmEmailWasRecived的任何客户端而言,确认步骤看起来好像对于我们在此处显示的所有版本都是同步的。这使得使用它的高级测试更容易编写,特别是如果测试中有在此检查之后的操作。这种代码应该存在于应用程序驱动层中,以便它可以被许多不同的测试用例重用。它的相对复杂性值得付出努力,因为它可以调整为高效且完全可靠,从而使所有依赖于它的测试也得到调整和可靠。

As far as any client of ConfirmEmailWasRecived is concerned, the confirmation step looks as though it is synchronous for all of the versions we show here. This makes the high-level test that uses it much simpler to write, particularly if there are actions in the test that follow this check. This sort of code should exist in the application driver layer so it can be reused by many different test cases. Its relative complexity is worth the effort because it can be tuned to be efficient and completely reliable, making all of the tests that depend upon it tuned and reliable too.

使用测试替身

Using Test Doubles

验收测试依赖于在类生产环境中执行自动化测试的能力。然而,这种测试环境的一个重要特性是它能够成功地支持自动化测试。自动验收测试与用户验收测试不同。其中一个区别是自动验收测试不应在包含与所有外部系统集成的环境中运行。相反,您的验收测试应该专注于提供一个可控的环境,让被测系统可以在其中运行。在这种情况下,“可控”意味着您能够为我们的测试创建正确的初始状态。与真实的外部系统集成消除了我们这样做的能力。

Acceptance testing relies on the ability to execute automated tests in a production-like environment. However, a vital property of such a test environment is that it is able to successfully support automated testing. Automated acceptance testing is not the same as user acceptance testing. One of the differences is that automated acceptance tests should not run in an environment that includes integration to all external systems. Instead, your acceptance testing should be focused on providing a controllable environment in which the system under test can be run. “Controllable” in this context means that you are able to create the correct initial state for our tests. Integrating with real external systems removes our ability to do this.

您应该在验收测试期间尽量减少外部依赖项的影响。然而,我们的目标是尽早发现问题,为此,我们的目标是不断整合我们的系统。显然这里存在紧张。与外部系统的集成可能很难正确进行,并且是问题的常见来源。这意味着仔细有效地测试此类集成点很重要。问题是,如果您将外部系统本身包括在您的验收测试范围内,您对系统及其启动状态的控制就会减少。此外,自动化测试的强度可能会在项目生命周期的早期对这些外部系统施加重大且意外的负载,这比负责它们的人员可能预期的要早得多。

You should work to minimize the impact of external dependencies during acceptance testing. However, our objective is to find problems as early as we can, and to achieve this, we aim to integrate our system continuously. Clearly there is a tension here. Integration with external systems can be difficult to get right and is a common source of problems. This implies that it is important to test such integration points carefully and effectively. The problem is that if you include the external systems themselves within the scope of your acceptance testing, you have less control over the system and its starting state. Further, the intensity of your automated testing can place significant and unexpected loads on those external systems much earlier in the life of the project than the people responsible for them may have expected.

图 8.4 外部系统的测试替身

Figure 8.4 Test doubles for external systems

图片

这种平衡行为通常会导致团队在测试策略中建立的某种妥协。与开发过程的任何其他方面一样,几乎没有“正确”的答案,而且项目会有所不同。我们的策略是双管齐下的:我们通常创建测试替身,代表与我们系统交互的所有外部系统的连接,如图 8.4所示。我们还围绕每个集成点构建小型测试套件,旨在在与这些外部系统有真正连接的环境中运行。

This balancing act usually results in a compromise of some sort that the team establishes as part of their testing strategy. As with any other aspect of your development process, there are few “right” answers, and projects will vary. Our strategy is two-pronged: We usually create test doubles that represent the connection to all external systems that our system interacts with, as shown in Figure 8.4. We also build small suites of tests around each integration point, intended to run in an environment that does have real connections to these external systems.

除了为我们提供建立测试基础的已知起点之外,创建测试替身以代替外部系统使用还有另一个优势:它为我们提供了应用程序中的附加点,我们可以在其中控制行为、模拟通信故障、模拟错误响应或负载下的响应等等——所有这些都完全在我们的控制之下。

In addition to providing us with the ability to establish a known starting point on which we can base tests, creating test doubles to use instead of external systems has another advantage: It provides us with additional points in the application where we can control behavior, simulate communications failures, simulate error responses or responses under load, and so on—all completely under our control.

良好的设计原则应该指导您尽量减少外部系统与您正在开发的系统之间的耦合。我们通常的目标是让我们系统的一个组件来表示与外部系统的所有交互——也就是说,每个外部系统一个组件(网关或适配器)。该组件将通信和与之相关的任何问题集中在一个地方,并将该通信的技术细节与系统的其余部分隔离开来。它还允许您实施模式来提高应用程序的稳定性,例如Release It!中描述的断路器模式。6个

Good design principles should guide you to minimize the coupling between external systems and the one that you are developing. We usually aim to have one component of our system to represent all interactions with an external system—that is, one component (a gateway or adapter) per external system. This component concentrates the communications and any problems associated with it into one place and isolates the technical details of that communication from the rest of the system. It also allows you to implement patterns to improve the stability of your application, such as the circuit breaker pattern described in Release It!6

该组件表示与外部系统的接口。无论公开的接口属于外部系统本身还是您的代码库的一部分,此接口都代表您需要证明有效的合同。需要从系统与其交互的角度以及作为与外部系统的真正通信点的角度来证明此接口。存根允许您断言您的系统与远程系统正确交互。我们接下来描述的集成测试允许您断言外部系统的行为符合您对交互的预期。从这个意义上讲,测试替身和交互测试一起工作以消除出错的机会。

This component represents an interface to the external system. Whether the interface that is exposed belongs to the external system itself or is part of your codebase, this interface represents the contract that you need to prove works. This interface needs to be proven both from the perspective of your system’s interactions with it and as a genuine point of communication with the external system. Stubs allow you to assert that your system interacts correctly with the remote system. Integration tests, which we describe next, allow you to assert that the external system behaves as you expect for your interactions. In this sense, test doubles and interaction tests work together to eliminate the chance of errors.

测试外部集成点

Testing External Integration Points

由于各种原因,外部系统的集成点是问题的常见来源。您的团队正在处理的代码可能会改变与成功沟通相关的某些内容。共享数据结构的变化在你的系统和外部系统之间,或者在消息交换的频率上,或者在寻址机制的配置上——几乎任何一种差异都可能是一个问题。通信另一端的代码也可能发生变化。

Integration points to external systems are a common source of problems for a variety of reasons. The code that your team is working on may change something relevant to successful communication. A change in the data structures shared between your system and the external system, or in the frequency of message exchange, or in the configuration of addressing mechanisms—almost any kind of difference could be a problem. The code at the other end of the communication may change too.

我们为断言此类集成点的行为而编写的测试应侧重于可能出现的问题,而这些将在很大程度上取决于集成的性质以及外部系统在其生命周期中所处的位置。如果外部系统成熟并投入生产,那么问题将与您在积极开发中面临的问题不同。这些因素将在某种程度上决定我们将在何时何地运行这些测试。

The tests that we write to assert the behavior of such integration points should be focused on the likely problems, and these will depend heavily upon the nature of the integration and where the external system is in its lifecycle. If the external system is mature and in production, the problems will be different to those you will face if it is in active development. These factors will dictate to some extent where and when we will run these tests.

如果外部系统正在积极开发中,则两个系统之间的接口的性质可能会发生变化。模式、合同等可能会改变,或者更微妙地,您交换信息内容的方式可能会改变。这种情况需要定期进行仔细测试,以确定两个团队的分歧点。根据我们的经验,在大多数集成中通常需要模拟一些明显的场景。我们建议您通过少量测试来涵盖这些明显的场景。这种策略会遗漏一些问题。我们的方法是通过编写测试来捕获每个案例来解决我们发现的问题。随着时间的推移,我们为每个集成点构建了一个小型测试套件,可以非常快速地捕获大多数问题。这个策略并不完美,

If the external system is under active development, there are likely to be changes in the nature of the interface between the two systems. Schemas, contracts, and so on may change, or, more subtly, the way in which the content of the information you exchange may change. Such a scenario needs careful testing on a regular basis to identify points at which the two teams diverge. In our experience, there are usually a few obvious scenarios to simulate in most integrations. We recommend that you cover these obvious scenarios with a small number of tests. This strategy will miss some problems. Our approach is to address breakages as we find them by writing a test to catch each case. Over time, we build a small suite of tests for each integration point that will catch most problems very quickly. This strategy is not perfect, but attempting to get perfect coverage in such scenarios is usually very difficult and the returns of effort versus reward diminish very quickly.

测试的范围应始终涵盖您的系统与外部系统的特定交互。他们不应该旨在全面测试外部系统接口。同样,这是基于收益递减法则:如果您不关心特定字段的存在与否,请不要对其进行测试。此外,请遵循我们在第 96 页的“集成测试”部分中提供的指南

Tests should always be scoped to cover the specific interactions that your system has with an external system. They should not aim to test the external system interface fully. Again, this is based on the law of diminishing returns: If you don’t care about the presence or absence of a particular field, don’t test for it. Also, follow the guidelines that we provide in the “Integration Testing” section on page 96.

正如我们所说,何时运行集成测试的时间无法确定。它因项目而异,因集成点而异。有时,集成点可以与验收测试同时运行,但通常情况并非如此。仔细考虑您将对外部系统提出的要求。请记住,您的测试每天将运行多次。如果每次与外部系统的交互测试都会产生真正的交互,那么您的自动化测试可能会在外部系统上产生类似生产的负载。这可能并不总是受欢迎的,特别是如果您的外部系统的提供者自己不做很多自动化测试。

As we have said, the timing of when to run integration tests can’t be fixed. It varies from project to project, and from integration point to integration point. Occasionally, integration points can sensibly be run at the same time as acceptance tests, but more often this is not the case. Think carefully about the demands that you will place on the external system. Remember that your tests will be running many times each day. If every interaction test with the external system results in a real interaction, your automated tests may create production-like load on the external system. This may not always be welcome, particularly if the providers of your external system don’t do much automated testing themselves.

缓解这种情况的一种策略是实施您的测试套件,这样它就不会在每次运行验收测试时都运行,而是可能每天一次或每周一次。例如,您可以将这些测试作为部署管道中的单独阶段运行,或者作为容量测试阶段的一部分运行。

One strategy to alleviate this is to implement your test suite so that it doesn’t run every time the acceptance tests run, but perhaps once a day, or once a week. You can run these tests as a separate stage in your deployment pipeline, or as part of the capacity test stage, for example.

验收测试阶段

The Acceptance Test Stage

一旦有了一套验收测试,它们就需要作为部署管道的一部分运行。规则是验收测试套件应该针对通过提交测试的每个构建运行。以下是适用于运行验收测试的一些实践。

Once you have a suite of acceptance tests, they need to be run as part of your deployment pipeline. The rule is that the acceptance test suite should be run against every build that passes the commit tests. Here are some practices applicable to running your acceptance tests.

未通过验收测试的构建将无法部署。在部署流水线模式中,只有通过了这个阶段的发布候选才可以部署到后续阶段。后期管道阶段通常被视为人为判断的问题:如果发布候选未能通过容量测试,在大多数项目中,有人会决定失败是否重要到足以立即结束候选人的旅程,或者是否允许尽管存在性能问题,它仍继续进行。验收测试不允许出现这种捏造的结果。通过意味着发布候选可以进步,失败意味着它永远不能。

A build that fails the acceptance tests will not be deployable. In the deployment pipeline pattern, only release candidates that have passed this stage are available for deployment to subsequent stages. Later pipeline stages are most commonly treated as a matter of human judgment: If a release candidate fails to pass capacity testing, on most projects someone decides whether the failure is important enough to end the journey of the candidate there and then, or whether to allow it to proceed despite the performance problem. Acceptance testing offers no room for such fudged results. A pass means that the release candidate can progress, a fail means that it never can.

由于这条硬线,验收测试门槛是一个极其重要的门槛,如果您的开发过程要顺利进行,就必须如此对待。保持复杂的验收测试运行需要您的开发团队花费时间。然而,这种成本是以投资的形式出现的,根据我们的经验,可以通过降低维护成本、允许您对应用程序进行广泛更改的保护以及显着提高的质量来多次偿还。这遵循我们在过程中提出痛苦的一般原则。我们从经验中知道,如果没有出色的自动验收测试覆盖率,就会发生以下三种情况之一:要么在您认为已经完成的过程结束时花费大量时间来尝试查找和修复错误,

Because of this hard line, the acceptance test gate is an extremely important threshold and must be treated as such if your development process is to continue smoothly. Keeping the complex acceptance tests running will take time from your development team. However, this cost is in the form of an investment which, in our experience, is repaid many times over in reduced maintenance costs, the protection that allows you to make wide-ranging changes to your application, and significantly higher quality. This follows our general principle of bringing the pain forward in the process. We know from experience that without excellent automated acceptance test coverage, one of three things happens: Either a lot of time is spent trying to find and fix bugs at the end of the process when you thought you were done, or you spend a great deal of time and money on manual acceptance and regression testing, or you end up releasing poor-quality software.

很难列举使项目保证对自动化验收测试进行这种投资的原因。对于我们通常参与的项目类型,我们的默认设置是自动验收测试和部署管道的实施通常是一个明智的起点。对于持续时间极短的小型团队(可能只有四个或更少的开发人员)的项目,这可能有点矫枉过正——您可能会运行一些端到端测试作为单阶段 CI 流程的一部分。但对于任何比这更大的东西,自动化验收测试为开发人员提供的对商业价值的关注是如此有价值,以至于值得付出代价。值得重申的是,大项目从小项目开始,当项目变大时,

It is difficult to enumerate the reasons that make a project warrant such an investment in automated acceptance testing. For the type of projects that we usually get involved in, our default is that automated acceptance testing and an implementation of the deployment pipeline are usually a sensible starting point. For projects of extremely short duration with a small team, maybe four or fewer developers, it may be overkill—you might instead run a few end-to-end tests as part of a single-stage CI process. But for anything larger than that, the focus on business value that automated acceptance testing gives to developers is so valuable that it is worth the costs. It bears repeating that large projects start out as small projects, and by the time a project gets large, it is invariably too late to retro-fit a comprehensive set of automated acceptance tests without a Herculean level of effort.

我们建议将由交付团队创建、拥有和维护的自动化验收测试作为所有项目的默认位置。

We recommend that the use of automated acceptance tests created, owned, and maintained by the delivery team should be the default position for all of your projects.

保持验收测试绿色

Keeping Acceptance Tests Green

由于运行有效的验收测试套件需要时间,因此在部署管道的后期运行它们通常是有意义的。这样做的问题是,如果开发人员不像等待提交测试那样坐在那里等待测试通过,那么他们通常会忽略验收测试失败。

Because of the time taken to run an effective acceptance test suite, it often makes sense to run them later in the deployment pipeline. The problem with this is that if the developers are not sitting there waiting for the tests to pass, as they are for the commit tests, so they often ignore acceptance test failures.

这种低效率是我们接受的部署管道的权衡,它使我们能够在提交测试关口非常快速地捕获大多数故障,同时还保持我们应用程序的良好自动化测试覆盖率。让我们快速解决这个反模式:归根结底,这是一个纪律问题,整个交付团队负责保持验收测试通过。

This inefficiency is the trade-off we accept for a deployment pipeline that allows us to catch most failures very quickly at the commit test gate, while also maintaining good automated test coverage of our application. Let’s address this antipattern quickly: Ultimately it is an issue of discipline, with the whole delivery team responsible for keeping the acceptance tests passing.

当验收测试失败时,团队需要停下来并立即对问题进行分类。它是脆弱的测试、配置不当的环境、由于应用程序更改而不再有效的假设,还是真正的失败?然后需要有人立即采取行动使测试再次通过。

When an acceptance test breaks, the team needs to stop and immediately triage the problem. Is it a fragile test, a poorly configured environment, an assumption that is no longer valid because of a change in the application, or a real failure? Then somebody needs to take immediate action to make the tests pass again.

如果让验收测试烂掉会怎样?当您接近发布时间时,您会尝试让您的验收测试通过,这样您就可以对软件的质量充满信心。通过验收测试,您发现很难区分验收测试和验收测试之间的区别失败是因为验收标准发生了变化,失败是因为代码已被重构并且测试之前与实现的耦合过于紧密,或者失败是因为应用程序的行为现在是错误的。在这些情况下,测试最终被删除或忽略是很常见的,因为没有足够的时间让代码考古学找出失败的原因。您最终会遇到持续集成应该解决的相同情况——最后匆忙让一切正常运行,但没有任何迹象表明需要多长时间,而且对代码的实际状态缺乏清晰度。

What happens if you let your acceptance tests rot? As you approach release time, you try to get your acceptance tests green so that you can feel confident about the quality of your software. Going through the acceptance tests, you discover that it is extremely hard to tell the difference between an acceptance test that is failing because the acceptance criteria changed, one that fails because the code has been refactored and the test was previously too tightly coupled to the implementation, or one that fails because the behavior of the application is now wrong. In these circumstances, it is common for tests to end up being deleted or ignored as there is not enough time for the code archaeology necessary to find out the reasons for the failure. You end up in the same situation that continuous integration was supposed to address—a rush at the end to get everything working, but without any indication of how long it will take, plus a lack of clarity about the actual state of the code.

必须尽快修复验收测试的问题,否则该套件将没有任何实际价值。最重要的一步是让故障可见。我们尝试了各种方法,例如构建主管追踪最有可能导致失败的更改的人员,向可能的罪魁祸首发送电子邮件,甚至站起来大喊“谁在修复验收测试构建?” (这很好用)。我们发现的最有效的方法是通过噱头,例如熔岩灯、大型构建监视器,或第 63 页“花哨的功能”部分中描述的其他技术之一。这里有一些方法可以使您的测试保持良好状态。

It is essential to fix acceptance test breakages as soon as possible, otherwise the suite will deliver no real value. The most important step is to make the failure visible. We have tried various approaches, such as build masters who track down the people whose changes are most likely to have caused the failure, emails to possible culprits, even standing up and shouting, “Who is fixing the acceptance test build?” (this works quite well). The most effective approach that we have found is through gimmicks, such as lava lamps, a large build monitor, or one of the other techniques described in the “Bells and Whistles” section on page 63. Here are some ways to keep your tests in good shape.

识别可能的罪魁祸首

Identify Likely Culprits

确定可能导致特定验收测试失败的原因并不像单元测试那么简单。单元测试将由单个开发人员或开发人员对的单次签入触发。如果你签入了一些东西,而构建在它之前工作时失败了,那么毫无疑问是你破坏了它。

Determining what may have caused a specific acceptance test failure is not as simple as for a unit test. A unit test will have been triggered by a single check-in by a single developer or a developer pair. If you check something in and the build fails when it was working before, there should be little doubt that it was you who broke it.

但是,由于在两次验收测试运行之间可能有多次提交,因此构建被破坏的机会更多。设计您的构建管道,以便您能够跟踪哪些更改与每个验收测试运行相关联,这是一个有价值的步骤。一些现代的持续集成系统使得在其生命周期中跟踪流水线构建变得简单,从而使解决这个问题变得相对简单。

However, since there can be several commits in between two acceptance test runs, there are more opportunities for the build to have been broken. Designing your build pipeline so that you are able to trace which changes are associated with each acceptance test run is a valuable step. Some modern continuous integration systems make it simple to be able to track pipelined builds through their lifecycle, and thus make it relatively straightforward to solve this problem.

部署测试

Deployment Tests

正如我们所描述的,一个好的验收测试的重点是证明满足特定故事或要求的特定验收标准。最好的验收测试是原子的——也就是说,它们会创建自己的开始条件并在结束时进行整理。这些理想的测试最大限度地减少了它们对状态的依赖,并且仅通过没有后门访问的可公开访问的渠道来测试应用程序。然而,有一些类型的测试在此基础上不合格,但是在验收测试关口运行它们非常有价值。

As we have described, a good acceptance test is focused on proving that a specific acceptance criterion for a specific story or requirement has been met. The best acceptance tests are atomic—that is, they create their own start conditions and tidy up at their conclusion. These ideal tests minimize their dependency on state and test the application only through publicly accessible channels with no back door access. However, there are some types of test that don’t qualify on this basis but that are, nevertheless, very valuable to run at the acceptance test gate.

当我们运行验收测试时,我们将测试环境设计为尽可能接近预期的生产环境。如果这样做并不昂贵,它们应该是相同的。否则尽量使用虚拟化来模拟你的生产环境。您使用的操作系统和任何中间件当然应该与生产相同,并且我们在开发环境中可能模拟或忽略的重要进程边界肯定会在这里表示。

When we run our acceptance tests, we design the test environment to be as close as reasonably achievable to the expected production environment. If it’s not expensive to do so, they should be identical. Otherwise use virtualization to simulate your production environment as far as possible. The operating system and any middleware you use should certainly be identical to production, and the important process boundaries that we may have simulated or ignored in our development environment will certainly be represented here.

这意味着除了测试我们的验收标准是否得到满足之外,这是我们确认我们到类似生产环境的自动化部署成功运行以及我们的部署策略有效的最早机会。我们经常选择运行一小部分新的冒烟测试,旨在断言我们的环境按照我们的预期配置,并且我们系统的各个组件之间的通信通道正确到位并按预期工作。我们有时将这些称为基础设施测试或环境测试,但它们实际上是部署测试,旨在表明部署已经成功,并为执行更多功能验收测试建立一个已知良好的起点。

This means that in addition to testing that our acceptance criteria have been met, this is the earliest opportunity for us to confirm that our automated deployment to a production-like environment works successfully and that our deployment strategy works. We often choose to run a small selection of new smoke tests designed to assert that our environment is configured as we expect and that the communications channels between the various components of our system are correctly in place and working as intended. We sometimes refer to these as infrastructure tests or environment tests, but what they really are is deployment tests intended to show that the deployment has been successful and to establish a known-good starting point for the execution of the more functional acceptance tests.

像往常一样,我们的目标是快速失败。我们希望验收测试构建在即将失败时尽快失败。出于这个原因,我们经常将部署测试视为一个特殊的套件。如果他们失败了,我们将立即使整个验收测试阶段失败,而不会等待通常冗长的验收测试套件完成其运行。这在测试异步系统时尤为重要,如果您的基础设施设置不正确,您的测试将在每个点执行到最大超时。我们的一个项目中的这种故障模式曾经导致等待 30 多个小时才能让验收测试运行全面失败——正常情况下大约 90 分钟即可完成的测试运行。

As usual, our objective is to fail fast. We want the acceptance test build to fail as quickly as possible if it is going to fail. For this reason, we often treat the deployment tests as a special suite. If they fail, we will fail the acceptance test stage as a whole immediately and won’t wait for the often lengthy acceptance test suite to complete its run. This is particularly important in testing asynchronous systems where, if your infrastructure is not correctly set up, your tests will execute to their maximum time-outs at every point. This failure mode on one of our projects once resulted in a wait of more than 30 hours for an acceptance test run to fail comprehensively—a test run that under normal circumstances would have completed in about 90 minutes.

这种按优先顺序排列的、快速失败的测试集合也是任何间歇性测试或定期捕获常见问题的测试的方便场所。正如我们之前所说,您应该找到可以捕获常见故障模式的提交级别测试,但有时当您考虑如何捕获常见但难以测试的问题时,此策略可以作为临时步骤。

This prioritized, fail-fast collection of tests is also a convenient place for any intermittent tests or tests that regularly catch common problems. As we have said before, you should find commit-level tests that can catch common failure modes, but sometimes this strategy can work as an interim step while you are thinking of how to catch a common, but awkward to test, problem.

验收测试性能

Acceptance Test Performance

由于我们的自动验收测试是为了断言我们的系统为我们的用户提供了预期的价值,所以他们的性能不是我们的主要关注点。首先创建部署管道的原因之一是验收测试通常需要很长时间才能运行,无法在提交周期中等待结果。有些人在哲学上反对这种观点,认为性能不佳的验收测试套件是验收测试套件维护不善的症状。让我们明确一点:我们认为不断维护您的验收测试套件以保持其良好的分解和连贯性很重要,但最终拥有一个全面的自动化测试套件比在十分钟内运行的测试套件更重要。

Since our automated acceptance tests are there to assert that our system delivers the expected value to our users, their performance is not our primary concern. One of the reasons for creating a deployment pipeline in the first place is the fact that acceptance tests usually take too long to run to wait for their results during a commit cycle. Some people are philosophically opposed to this point of view, arguing that a poorly performing acceptance test suite is a symptom of a poorly maintained acceptance test suite. Let us be clear: We think it is important to constantly tend your acceptance test suite to keep it well factored and coherent, but ultimately it is more important to have a comprehensive automated test suite than one that runs in ten minutes.

验收测试必须断言系统的行为。他们必须尽可能地从外部用户的角度来做到这一点,而不仅仅是测试其中某个隐藏层的行为。即使对于相对简单的系统,这也自动意味着性能损失。在我们甚至考虑运行单个测试所需的时间之前,必须部署、配置、启动和停止系统及其所有适当的基础设施。

Acceptance tests must assert the behavior of the system. They must do that, as far as possible, from an external user’s viewpoint and not just by testing the behavior of some hidden layer within it. This automatically implies performance penalties even for relatively simple systems. The system and all of its appropriate infrastructure must be deployed, configured, started, and stopped, before we even consider the time it takes to run a single test.

然而,一旦您开始实施部署管道,快速失败系统和快速反馈周期就会开始显示它们的价值。从引入问题到发现问题之间的时间越长,就越难找到问题的根源并加以解决。通常,验收测试套件需要几个小时而不是几分钟才能完成。这当然是一种可行的状态;许多项目在多小时的验收测试阶段工作得很好。但是你可以更有效率。通过减少从验收测试阶段获得结果所需的时间,您可以应用一系列技术来提高团队的整体效率。

However, once you start down the path of implementing a deployment pipeline, fail-fast systems and rapid feedback cycles begins to show their value. The longer the time between the point where a problem is introduced and the point of discovering it, the more difficult it will be to find the source of the problem and fix it. Typically, acceptance test suites take several hours to complete rather than a few minutes. This is certainly a workable state; many projects work very well with multihour acceptance test stages. But you can be more efficient. There is a spectrum of techniques that you can apply to improve the overall efficiency of the team by cutting down the time it takes to get a result from the acceptance test stage.

重构常见任务

Refactor Common Tasks

显而易见的第一步是通过保留最慢测试的列表并定期花一点时间在它们上以找到提高它们效率的方法来寻找速效方法。这与我们建议的管理单元测试的策略完全相同。

The obvious first step is to look for quick wins by keeping a list of the slowest tests and regularly spending a little time on them to find ways to make them more efficient. This is precisely the same strategy that we advised for managing unit tests.

在此之上的一个步骤是寻找通用模式,尤其是在测试设置中。一般来说,就其本质而言,验收测试比单元测试更有状态。由于我们建议您采用端到端的方法进行验收测试并尽量减少共享状态,这意味着每个验收测试都应该设置自己的开始条件。通常,此类测试设置中的特定步骤在许多测试中都是相同的,因此值得多花一些时间来确保这些步骤有效。如果可以使用公共 API 而不是通过 UI 执行此类设置,那是理想的选择。有时,使用“种子数据”预填充应用程序或使用某些后门进入应用程序以使用测试数据填充它是一种有效的方法,但您应该以一定程度的怀疑态度对待此类后门,

One step up from this is to look for common patterns, particularly in the test setup. In general, by their nature, acceptance tests are much more stateful than unit tests. Since we recommend that you take an end-to-end approach to acceptance tests and minimize shared state, this implies that each acceptance test should set up its own start conditions. Frequently, specific steps in such test setup are the same across many tests, so it is worth spending some extra time on ensuring that these steps are efficient. If there is a public API that can be used instead of performing such setup through the UI, then that is ideal. Sometimes, prepopulating the application with “seed data” or using some back door into the application to populate it with test data is a valid approach, but you should treat such back doors with a degree of skepticism, since it is all too easy for this test data to not be quite the same as that created by the normal operation of the application, which invalidates the correctness of subsequent testing.

无论采用何种机制,通过创建测试助手类来重构测试以确保它们为常见任务执行的代码是相同的,这是朝着更好的性能和更高的测试可靠性迈出的重要一步。

Whatever the mechanism, refactoring tests to ensure that the code that they execute for common tasks is the same through the creation of test helper classes is an important step toward better performance and greater reliability in tests.

共享昂贵的资源

Share Expensive Resources

我们已经在前面的章节中描述了一些技术来为提交阶段测试中的测试实现合适的起始状态。这些技术可以适用于验收测试,但验收测试的黑盒性质排除了一些选项。

We have already described some techniques to achieve a suitable starting state for tests in commit stage testing in earlier chapters. These techniques can be adapted to acceptance testing, but the black box nature of acceptance tests rules some options out.

解决此问题的直接方法是在测试开始时创建应用程序的标准空白实例,并在测试结束时丢弃它。然后测试将完全负责用它需要的任何起始数据填充这个实例。这是简单且非常可靠的,具有每次测试都从已知的、完全可重现的起点开始的宝贵特性。不幸的是,对于我们创建的大多数系统来说,它也非常慢,因为对于除了最简单的软件系统之外的任何系统,它都需要花费大量时间来清除任何状态并首先启动应用程序。

The straightforward approach to this problem is to create a standard blank instance of the application at the start of the test and discard it at the end. The test is then wholly responsible for populating this instance with any start data it needs. This is simple and very reliable, having the valuable property that each test is starting from a known, completely reproducible starting point. Unfortunately, for most systems that we create it is also very slow, because for anything but the simplest software systems it takes a significant amount of time to clear any state and start the application in the first place.

因此必须妥协。我们需要选择我们将在测试之间共享哪些资源,以及我们将在单个测试的上下文中管理哪些资源。通常,对于大多数基于服务器的应用程序,可以从共享开始系统本身的一个实例。在验收测试运行开始时创建被测系统的干净运行实例,针对该实例运行所有验收测试,并在结束时将其关闭。根据被测系统的性质,有时可以优化其他耗时的资源,使验收测试套件作为一个整体运行得更快。

It is therefore necessary to compromise. We need to pick which resources we will share between tests and which we will manage within the context of a single test. Typically, for most server-based applications, it is possible to start by sharing an instance of the system itself. Create a clean running instance of the system under test at the beginning of the acceptance test run, run all of the acceptance tests against that instance, and shut it down at the end. Depending on the nature of the system under test, there are sometimes other time-consuming resources that can be optimized to make the acceptance test suite as a whole run faster.

并行测试

Parallel Testing

当您的验收测试隔离良好时,另一种加快速度的可能性就会出现:并行运行测试。对于多用户、基于服务器的系统,这是一个显而易见的步骤。如果您可以划分测试,使它们之间没有交互的风险,那么针对系统的单个实例并行运行测试将显着减少整体验收测试阶段的持续时间。

When the isolation of your acceptance tests is good, another possibility to speed things up presents itself: running the tests in parallel. For multiuser, server-based systems this is an obvious step. If you can divide your tests so that there is no risk of interaction between them, then running the tests in parallel against a single instance of the system will provide a significant decrease in the duration of your acceptance test stage overall.

使用计算网格

Using Compute Grids

对于非多用户系统,对于本身就很昂贵的测试,或者对于模拟许多并发用户很重要的测试,使用计算网格具有巨大的好处。当结合使用虚拟服务器时,这种方法变得非常灵活和可扩展。在极限情况下,您可以为每个测试分配自己的主机,因此验收测试套件只需要最慢的测试。

For systems that are not multiuser, for tests that are expensive in their own right, or for tests where it is important to simulate many concurrent users, the use of compute grids is of enormous benefit. When combined with the use of virtual servers, this approach becomes exceedingly flexible and scalable. At the limit, you could allocate each test its own host, so the acceptance test suite would only take as long as its slowest test.

在实践中,更受约束的分配策略通常更有意义。该领域的一些供应商并没有失去这一优势。大多数现代 CI 服务器都提供了管理测试服务器网格的工具,仅用于这个目的。如果您使用的是 Selenium,另一种选择是使用开源 Selenium Grid,它允许使用为使用 Selenium Remoting 而编写的未经修改的验收测试,以便在计算网格上并行执行。

In practice, more constrained allocation strategies usually make more sense. This advantage has not been lost on some of the vendors in this space. Most modern CI servers provide the facility to manage a grid of test servers for just this purpose. If you are using Selenium, another alternative is to use the open source Selenium Grid, which allows for the use of unmodified acceptance tests written to use Selenium Remoting to be executed in parallel on a compute grid.

概括

Summary

验收测试的使用是对开发过程有效性的重要补充。它的作用是将交付团队的所有成员的注意力集中在真正重要的事情上:用户需要从系统中获得的行为。

The use of acceptance testing is an important addition to the effectiveness of your development process. It acts to focus the attention of all members of a delivery team on what really counts: the behavior that the users need from the system.

自动化验收测试通常比单元测试更复杂。他们需要更多的时间来维护,而且他们可能会比单元测试花费更多的时间来解决问题,因为修复故障和让验收测试套件通过之间存在固有的滞后。然而,当从用户的角度用作系统行为的保证时,它们提供了一种宝贵的防御措施,可以防止在任何复杂的任何应用程序的生命周期中出现的回归问题。

Automated acceptance tests are usually more complex than unit tests. They take more time to maintain, and they will probably spend more of their life being broken than unit tests do because of the inherent lag between fixing a failure and having the acceptance test suite pass. However, when used as a guarantee of the behavior of the system from a user’s perspective, they offer an invaluable defense against regression problems that will arise during the life of any application of any complexity.

以任何有意义的方式将一个软件项目与另一个软件项目进行比较是极其困难的,如果不是不可能的话,这使得我们很难向您提供任何支持我们断言的数据——使用自动验收测试会为自己付出很多倍的代价。我们只能向您保证,尽管在许多项目中保持验收测试运行是一项艰巨的工作,并且使我们面临一些复杂的问题,但我们从未后悔使用它们。事实上,他们经常通过提供安全地更改我们系统的大部分内容的工具来拯救我们。我们仍然坚信,这样的重点开发团队内部的测试鼓励是成功软件交付的重要组成部分。我们建议您尝试采用我们在本章中描述的对验收测试的关注,并亲自看看是否值得。

The extreme difficulty, if not impossibility, of measuring one software project against another in any meaningful way makes it hard for us to supply you with any data that backs our assertion—that the use of automated acceptance testing will pay for itself many times over. We can only assure you that, despite having worked on many projects where keeping the acceptance tests running was hard work and exposed us to some complex problems, we have never regretted their use. Indeed, they have often saved us by providing the facility to change large parts of our system safely. We still strongly believe that the focus that such testing encourages within a development team is a powerful ingredient of successful software delivery. We recommend that you try adopting the focus on acceptance testing that we describe in this chapter and see for yourself if it is worthwhile.

采用纪律拒绝任何无法通过验收测试门的发布候选者是另一种做法,在我们看来,这代表了交付团队的输出质量向前迈出了重要一步。

Adopting the discipline to reject any release candidate that is unable to pass the acceptance test gate is another practice that in our opinion represents a significant step forward in the quality of the output of a delivery team.

我们在软件行业的经验是,手动测试是常态,并且通常代表团队采用的唯一测试形式。我们发现手动测试既非常昂贵,又很难单独确保高质量的结果。手动测试当然有其用武之地:探索性测试、可用性测试、用户验收测试、展示。但是,人类根本无法有效地完成手动回归测试要求他们完成的平凡、重复但复杂的任务——至少不会感到痛苦。低质量的软件是这种低质量过程的必然结果。

Our experience of the software industry is that manual testing is the norm and often represents the only form of testing adopted by a team. We have found that manual testing is both prohibitively expensive and rarely good enough on its own to ensure a high-quality result. Manual testing, of course, has its place: exploratory testing, usability testing, user acceptance testing, showcasing. But human beings are simply not equipped to work effectively at the mundane, repetitive but complex tasks that manual regression testing requires of them—at least without feeling miserable. Poor-quality software is the inevitable outcome of such a poor-quality process.

近年来,对单元测试的更多关注帮助提高了一些团队的游戏水平。这是超越仅依赖手动测试的重要一步,但根据我们的经验,它仍然会导致代码无法执行用户希望它执行的操作。单元测试不以业务为中心。我们认为,采用由验收标准驱动的测试代表了更进一步,通过

In recent years, an increased focus on unit testing has helped raise the game for some teams. This is a significant step beyond reliance on only manual testing, but in our experience it can still result in code that doesn’t do what the users wanted it to do. Unit testing is not business-focused. We believe that the adoption of tests driven by acceptance criteria represents a further step forward, by

• 增强对软件适用性的信心

• Increasing confidence that the software is fit for purpose

• 针对系统的大规模更改提供保护

• Providing protection against large-scale changes to the system

• 通过全面的自动化回归测试显着提高质量

• Significantly improving quality through comprehensive automated regression testing

• 每当出现缺陷时提供快速可靠的反馈,以便立即修复

• Providing fast and reliable feedback whenever a defect occurs so that it can be fixed immediately

• 让测试人员腾出时间来设计测试策略、制定可执行规范以及执行探索性和可用性测试

• Freeing up testers to devise testing strategies, develop executable specifications, and perform exploratory and usability testing

• 缩短周期时间并实现持续部署

• Reducing cycle time and enabling continuous deployment

第 9 章测试非功能性需求

Chapter 9. Testing Nonfunctional Requirements

介绍

Introduction

我们已经描述了自动化应用程序测试的各个方面,作为实现部署管道过程的一部分。然而,到目前为止,我们的重点主要是测试那些通常被描述为功能需求的应用程序行为。在本章中,我们将描述我们测试非功能性需求的方法,特别关注测试容量、吞吐量和性能。

We have described various aspects of automating the testing of an application as part of the process of implementing a deployment pipeline. However, so far our focus has been mostly on testing those behaviors of the application commonly described as functional requirements. In this chapter, we will describe our approach to testing nonfunctional requirements, with a specific focus on testing capacity, throughput, and performance.

首先,让我们澄清一些关于术语的混淆。我们将使用与 Michael Nygard 相同的术语。1换句话说,性能是衡量处理单个事务所用时间的指标,可以单独或在负载下进行衡量。吞吐量是系统在给定时间跨度内可以处理的事务数。它总是受到系统中某些瓶颈的限制。对于给定的工作负载,系统可以维持的最大吞吐量,同时为每个单独的请求保持可接受的响应时间,就是它的容量客户通常对吞吐量或容量感兴趣。在现实生活中,“性能”经常被用作包罗万象的术语;我们将在本章中更加小心。

First of all, let’s clear up some confusion around the terms. We’ll use the same terminology as Michael Nygard.1 To paraphrase, performance is a measure of the time taken to process a single transaction, and can be measured either in isolation or under load. Throughput is the number of transactions a system can process in a given timespan. It is always limited by some bottleneck in the system. The maximum throughput a system can sustain, for a given workload, while maintaining an acceptable response time for each individual request, is its capacity. Customers are usually interested in throughput or capacity. In real life, “performance” is often used as a catch-all term; we will try to be rather more careful in this chapter.

非功能性需求 (NFR) 很重要,因为它们会给软件项目带来重大的交付风险。即使您清楚自己的非功能性需求是什么,也很难找到做足够工作以确保满足这些需求的最佳点。许多系统失败是因为它们无法应对施加在它们身上的负载、不安全、运行速度太慢,或者,也许最常见的是,由于代码质量差而变得无法维护。有些项目之所以失败,是因为他们走向了另一个极端,过于担心 NFR,以至于开发过程太慢,或者系统变得如此复杂和过度设计,以至于没有人能弄清楚如何有效或适当地开发。

Nonfunctional requirements (NFRs) are important because they present a significant delivery risk to software projects. Even when you are clear about what your nonfunctional requirements are, it is very difficult to hit the sweet spot of doing just enough work to ensure that they are met. Many systems fail because they weren’t able to cope with the load applied to them, were not secure, ran too slowly, or, perhaps most common of all, became unmaintainable because of poor code quality. Some projects fail because they go to the other extreme and worry so much about the NFRs that the development process is too slow, or the system becomes so complex and over-engineered that no one can work out how to develop efficiently or appropriately.

因此,在许多方面,将需求划分为功能性和非功能性是人为的。可用性、容量、安全性和可维护性等非功能性需求与功能性需求一样重要和有价值,它们对于系统的运行至关重要。这个名称具有误导性——已经提出了诸如跨功能需求和系统特征之类的替代方案——而且根据我们的经验,通常处理它们的方式很少能很好地发挥作用。项目的利益相关者应该能够优先考虑是否实施允许系统接受信用卡支付的功能,而不是实施允许 1,000 个并发用户访问它的功能。一个可能真的比另一个对企业更有价值。

Thus, in many ways, the division of requirements into functional and nonfunctional is an artificial one. Nonfunctional requirements such as availability, capacity, security, and maintainability are every bit as important and valuable as functional ones, and they are essential to the functioning of the system. The name is misleading—alternatives such as cross-functional requirements and system characteristics have been suggested—and, in our experience, the way in which they are commonly dealt with rarely works very well. The stakeholders in a project should be able to make a priority call on whether to implement the feature that allows the system to take credit card payments as opposed to the feature that allows 1,000 concurrent users to access it. One may genuinely be of more value to the business than the other.

必须在项目开始时确定哪些非功能性需求很重要。然后,团队需要找到一种方法来衡量它们,并将对它们的定期测试纳入交付计划,并在适当的情况下纳入部署管道。我们从讨论非功能性需求的分析开始本章。然后我们讨论如何开发您的应用程序以满足其容量需求。接下来,我们将介绍如何测量容量以及如何创建进行测量的环境。最后,我们讨论了从您的自动化验收测试套件创建容量测试以及将非功能测试合并到部署管道中的策略。

It’s essential to identify which nonfunctional requirements are important at the beginning of the project. Then, the team needs to find a way to measure them and incorporate regular testing of them into the delivery schedule and, where appropriate, the deployment pipeline. We start off this chapter by discussing the analysis of nonfunctional requirements. We then talk about how to develop your application in such a way as to meet its capacity requirements. Next, we cover how to measure capacity and how to create an environment in which to conduct the measurements. Finally, we discuss the strategies for creating capacity tests from your automated acceptance test suite and for incorporating nonfunctional testing into the deployment pipeline.

管理非功能性需求

Managing Nonfunctional Requirements

从某种意义上说,非功能性需求 (NFR) 与其他需求一样:它们可以具有真正的商业价值。从另一种意义上说,它们是不同的,因为它们倾向于跨越其他需求的界限。许多 NFR 的交叉性质使得它们在分析和实施方面都难以处理。

In one sense, nonfunctional requirements (NFRs) are the same as any others: They can have real business value. In another sense, they are different, in that they tend to cross the boundaries of other requirements. The crosscutting nature of many NFRs makes them hard to handle both in terms of analysis and in terms of implementation.

将系统的非功能性需求与功能性需求区别对待的困难在于,这很容易将它们从项目计划中删除或对它们的分析给予足够的重视。这可能是灾难性的,因为 NFR 是项目风险的常见来源。在交付过程的后期发现应用程序由于存在基本安全漏洞或极差的性能而无法达到目的的情况太常见了——这可能会导致项目延迟甚至被取消。

The difficulty with treating nonfunctional requirements of the system differently from functional requirements is that it makes it easy to drop them off the project plan or to pay insufficient attention to their analysis. This may be disastrous because NFRs are a frequent source of project risk. Discovering late in the delivery process that an application is not fit for purpose because of a fundamental security hole or desperately poor performance is all too frequent—and may cause projects to be late or even to get cancelled.

在实施方面,NFR 很复杂,因为它们通常对系统架构有非常大的影响。例如,任何需要高性能的系统都不应该涉及跨越多个层的请求。由于系统的架构在交付过程的后期很难更改,因此在项目开始时考虑非功能性需求至关重要。这意味着预先进行足够的分析,以便就为系统选择哪种架构做出明智的决定。

In terms of implementation, NFRs are complex because they usually have a very strong influence on the architecture of the system. For example, any system that requires high performance should not involve requests traversing several tiers. Since the architecture of the system is hard to change later on in the delivery process, it is essential to think about nonfunctional requirements at the beginning of the project. This means doing just enough analysis up front to make an informed decision on what architecture to choose for the system.

此外,NFR 往往以一种无益的方式相互交互:非常安全的系统通常会在易用性上妥协;非常灵活的系统通常会在性能等方面做出妥协。我们的观点是,虽然在理想世界中每个人都希望他们的系统高度安全、性能非常高、非常灵活、可扩展性极强、易于使用、易于支持以及易于开发和维护,但实际上每一个都如此特性是有代价的。每个架构都涉及非功能性需求之间的某种权衡——因此软件工程协会的架构权衡分析方法 (ATAM) 旨在帮助团队通过对其 NFR(称为“质量属性”)的全面分析来决定合适的架构。

In addition, NFRs tend to interact with one another in an unhelpful manner: Very secure systems often compromise on ease of use; very flexible systems often compromise on performance, and so forth. Our point here is that, while in an ideal world everyone wants their systems to be highly secure, very highperformance, massively flexible, extremely scalable, easy to use, easy to support, and simple to develop and maintain, in reality every one of these characteristics comes at a cost. Every architecture involves some trade-off between nonfunctional requirements—hence the Software Engineering Institute’s Architectural Tradeoff Analysis Method (ATAM) designed to help teams decide upon a suitable architecture by a thorough analysis of its NFRs (referred to as “quality attributes”).

总之,在项目开始时,参与交付的每个人——开发人员、运维人员、测试人员和客户——都需要考虑应用程序的 NFR 及其对系统架构、项目进度、测试策略的影响,和总成本。

In summary, at the beginning of the project, everybody involved in delivery—developers, operations personnel, testers, and the customer—need to think through the application’s NFRs and the impact they may have on the system architecture, project schedule, test strategy, and overall cost.

分析非功能性需求

Analyzing Nonfunctional Requirements

对于正在进行的项目,我们有时会捕获 NFR 作为功能性故事的常规验收标准,我们预计不需要大量额外的工作来满足它们。但这通常是一种笨拙且低效的管理方式。相反,为非功能性需求创建特定的故事或任务集通常是有意义的,尤其是在项目开始时。由于我们的目标是最大限度地减少我们必须处理横切关注点的程度,因此需要结合使用这两种方法——创建特定任务来管理非功能性需求以及向其他需求添加非功能性验收标准。

For a project that is in flight, we sometimes capture NFRs as regular acceptance criteria for functional stories where we don’t anticipate that a significant additional effort will be required to meet them. But this can often be an awkward and inefficient way of managing them. It often makes sense, instead, to create specific sets of stories or tasks for nonfunctional requirements as well, especially at the beginning of a project. Since our aim is to minimize the degree to which we have to deal with crosscutting concerns, a blend of both approaches—creating specific tasks to manage nonfunctional requirements as well as adding nonfunctional acceptance criteria to other requirements—is needed.

例如,管理 NFR 的一种方法(例如可审计性)是说“与系统的所有重要交互都应该被审计”,并且可能会创建一个策略,将相关的验收标准添加到涉及需要审计的交互的故事中。被审计。另一种方法是从审计员的角度捕捉需求。担任该角色的用户希望看到什么?我们简单地描述审计员对他们希望看到的每份报告的要求。这样,审计就不再是一个横切的非功能性需求;相反,我们将它与任何其他需求一样对待,因此它可以与其他需求一样进行测试和优先级排序。

For example, one approach to managing an NFR, such as auditability, is to say something like “All important interactions with the system should be audited,” and perhaps create a strategy for adding relevant acceptance criteria to the stories involving the interactions that need to be audited. An alternative approach is to capture requirements from the perspective of an auditor. What would a user in that role like to see? We simply describe the auditor’s requirements for each report they want to see. This way, auditing is no longer a crosscutting nonfunctional requirement; instead, we treat it the same as any other so it may be tested and prioritized on par with other requirements.

容量等特性也是如此。将您对系统的期望定义为定量的故事,并足够详细地指定它们是有意义的,这样您就可以执行成本效益分析,从而相应地确定它们的优先级。根据我们的经验,它也往往会导致需求得到更有效的管理,从而使用户和客户更满意。对于最常见的非功能性需求类别:安全性、可审核性、可配置性等,此策略可以帮助您走得更远。

The same is true of characteristics like capacity. It makes sense to define your expectations of the system as stories, quantitatively, and specify them in enough detail so you can perform a cost-benefit analysis and thus prioritize them accordingly. In our experience, it also tends to result in the requirement being much more efficiently managed, and therefore in happier users and customers. This strategy can take you quite far for most common classes of nonfunctional requirements: security, auditability, configurability, and so on.

在分析 NFR 时,必须提供合理的详细程度。仅仅说您对响应时间的要求是“尽可能快”是不够的。“尽快”对可以合理应用的努力或预算没有限制。“尽可能快”是否意味着要小心缓存的方式和缓存内容,还是意味着制造自己的 CPU,就像 Apple 为 iPad 所做的那样?所有需求,无论是功能性的还是非功能性的,都必须分配一个值,以便可以对其进行估计和优先级排序。这种方法迫使团队思考将开发预算花在哪里最合适。

It is essential to supply a reasonable level of detail when analyzing NFRs. It is not enough to say that your requirement for response time is “as fast as possible.” “As fast as possible” puts no cap on the effort or budget that can sensibly be applied. Does “as fast as possible” mean being careful about how and what you cache, or does it mean manufacturing your own CPU, as Apple did for the iPad? All requirements, whether functional or not, must be assigned a value so it is possible to estimate and prioritize them. This approach forces teams to do the thinking about where the development budget is best spent.

许多项目都面临着应用程序的验收标准不是特别好理解的问题。他们将有明显定义明确的声明,例如“所有用户交互将花费不到两秒钟的时间来响应”或“系统将每小时处理 80,000 笔交易”。这样的定义对于我们的需要来说太笼统了。关于“应用程序性能”的空谈通常被用作描述性能要求、可用性要求和许多其他要求的简写方式。如果我们声明我们的应用程序应在两秒内响应,这是否意味着在所有情况下都如此?如果我们的一个数据中心发生故障,我们还需要满足两秒的阈值吗?该阈值是否仍然与很少使用的交互相关,或者仅与常见交互相关?当我们说两秒钟时,这是否意味着两秒后交互成功结束,或者仅两秒后用户收到某种反馈?如果出现问题,我们是否需要在两秒内响应错误消息,还是仅针对成功案例?当系统承受压力、处理其峰值负载或平均响应时间时,是否要满足两秒的要求?

Many projects face the problem of the acceptance criteria of the application being not particularly well understood. They will have apparently well-defined statements like “All user interactions will take less than two seconds to respond” or “The system will process 80,000 transactions per hour.” Such definitions are too general for our needs. Wooly talk about “application performance” is often used as a shorthand way of describing performance requirements, usability requirements, and many others. If we state that our application should respond in under two seconds, does that mean in all circumstances? If one of our datacenters fails, do we still need to meet the two second threshold? Is that threshold still relevant for rarely used interactions, or just the common ones? When we say two seconds, does that mean two seconds to successful conclusion of the interaction, or just two seconds before the user receives some kind of feedback? Do we need to respond within two seconds with an error message if something goes wrong, or is it just for the success cases? Is the two seconds requirement to be met when the system is under stress, dealing with its peak load, or an average response time?

性能需求的另一种常见误用是作为描述系统可用性的懒惰方式。许多人说“在两秒钟内做出响应”时通常的意思是“我不想在没有任何反馈的情况下坐在电脑前太久。” 当可用性问题被识别为可用性问题而不是伪装成性能需求时,它们最好得到处理。

Another common misuse of performance requirements is as a lazy way to describe the usability of the system. What many people often mean when they say “Respond in two seconds” is “I don’t want to sit in front of a computer without any feedback for too long.” Usability problems are best dealt with when they are recognized as such, not disguised as performance requirements.

容量编程

Programming for Capacity

非功能性需求分析不当的问题在于它们往往会限制思维并经常导致过度设计和不适当的优化。花费过多时间编写“高性能”代码太容易了。程序员很难预测应用程序的性能瓶颈在哪里。它们往往会使代码变得不必要地复杂,从而导致维护成本高昂,以期获得令人怀疑的性能提升。值得完整引用 Donald Knuth 的著名格言:

The problem with poorly analyzed nonfunctional requirements is that they tend to constrain thinking and often lead to overdesign and inappropriate optimization. It is too easy to spend excessive amounts of time on writing “performant” code. Programmers are fairly poor at predicting where the performance bottlenecks in an application are. They tend make code unnecessarily complex, and thus costly to maintain, in an effort to achieve doubtful performance gains. It is worth quoting in full Donald Knuth’s famous dictum:

我们应该忘记小效率,比如说,大约 97% 的时间:过早的优化是万恶之源。然而,我们不应该放弃那关键的 3% 的机会。一个好的程序员不会被这样的推理哄骗而沾沾自喜,他会明智地仔细查看关键代码;但只有在识别出该代码之后。

We should forget about small efficiencies, say, about 97% of the time: Premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%. A good programmer will not be lulled into complacency by such reasoning, he will be wise to look carefully at the critical code; but only after that code has been identified.

关键的短语是最后一个。在找到解决方案之前,必须确定问题的根源。在此之前,我们需要知道我们有一个问题。容量测试阶段的重点是告诉我们是否有问题,以便我们继续修复它。不要猜测;措施。

The crucial phrase is the last one. Before a solution can be found, the source of the problem has to be identified. Before that happens, we need to know that we have a problem at all. The point of the capacity testing stage is to tell us whether we have a problem, so that we can go on to fix it. Don’t guess; measure.

过早和过多地关注优化应用程序的容量是低效、昂贵的,而且很少能提供高性能的系统。在最极端的情况下,它可以阻止项目完全交付。

Focusing too early and too heavily on optimizing the capacity of the application is inefficient, expensive, and rarely delivers a high-performance system. At its most extreme, it can prevent a project from delivering altogether.

事实上,为高容量系统编写的代码必然比为日常系统编写的代码简单。复杂性增加了延迟,但大多数程序员发现这很难理解,更不用说采取行动了。本书并不是专门针对高性能系统设计的专着,但这里是我们所用方法的大致概述——在此呈现是为了将容量测试置于交付过程的背景中。

In fact, code written for high-capacity systems is, by necessity, simpler than that written for everyday systems. Complexity adds delays, but most programmers find this difficult to understand, let alone act upon. This book is not really a place for a treatise on the design of high-performance systems, but here is a broad outline of the approach that we have used—presented here in order to put capacity testing into context in the delivery process.

在任何系统的设计中,瓶颈都会存在于系统性能受限的地方。有时,这些瓶颈很容易预测,但更多时候并非如此。在项目启动时,识别容量问题的最常见原因并努力避免遇到这些问题是明智的。大多数现代系统所做的最昂贵的事情是通过网络进行通信或将数据存储在磁盘上。跨进程和网络的通信边界在性能方面的成本非常高,并且会影响应用程序的稳定性,因此应尽量减少此类通信。

In the design of any system, bottlenecks will exist where performance of the system is constrained. Sometimes, these bottlenecks are easy to predict, but more often they are not. At the initiation of a project, it is sensible to recognize the most common causes of capacity problems and work to avoid running into them. The most costly things that most modern systems do is communicate across a network or store data on a disk. Communication across process and network boundaries is very costly in terms of performance and impacts application stability, so such communication should be minimized.

编写大容量软件需要比其他类型的系统更多的纪律,并且对支持应用程序的底层硬件和软件如何工作有一定程度的机械同情。高性能需要额外的成本,必须理解这种额外成本,并根据增加的性能带来的业务价值进行权衡。对能力的关注往往会迎合技术人员的心态。它可以带出我们最坏的一面,成为过度设计解决方案的最有可能的原因,从而增加项目成本。尝试将有关系统容量特征的决策交到业务发起人手中是极其重要的。我们想重申一个事实,即高性能软件实际上更简单,而不是更复杂。

Writing high-capacity software requires more discipline than other types of systems and a degree of mechanical sympathy for how the underlying hardware and software supporting your application works. High performance comes at an extra cost, and this additional cost must be understood and weighed against the business value that added performance brings. A focus on capacity often panders to the mindset of technical people. It can bring out the worst in us, becoming the most likely cause of over-engineered solutions and so inflated project costs. It is extremely important to try and place decisions on the capacity characteristics of a system into the hands of the business sponsors. We would like to reiterate the fact that high-performance software is in fact simpler, not more complex. The trouble is that it can take extra work to find a simple solution to a problem.

需要取得平衡。构建高容量系统是棘手的,并且天真地假设您以后能够解决所有问题也不是成功的好策略。一旦应用程序的最初的、可能广泛的性能问题在定义架构的级别得到处理以最小化跨进程边界交互,就应该避免在开发过程中进行更详细的“优化”,除非它们正在修复一个明确识别和可衡量的问题问题。这就是经验值钱的地方。为了成功,您必须避免两个极端:一方面,假设您以后能够解决所有容量问题;另一方面,由于担心未来的容量问题,编写防御性的、过于复杂的代码。

There is a balance to be struck. Building high-capacity systems is tricky, and making the naive assumption that you will be able to fix all of the problems later is also not a great strategy for success. Once the initial, likely broad, performance issues of the application are dealt with at the level of defining an architecture to minimize cross-process-boundary interactions, more detailed “optimizations” during development should be avoided unless they are fixing a clearly identified and measurable problem. This is where experience pays. In order to succeed, you must avoid two extremes: at one end, the assumption that you will be able to fix all capacity issues later; at the other end, writing defensive, overcomplex code in fear of future capacity problems.

我们的策略是通过以下方式解决容量问题:

Our strategy is to address capacity problems in the following ways:

1. 确定您的应用程序的体系结构。通常要特别注意进程和网络边界以及 I/O。

1. Decide upon an architecture for your application. Pay particular attention to process and network boundaries and I/O in general.

2. 理解和使用模式,避免影响系统稳定性和容量的反模式。Michael Nygard 的优秀作品Release It! 详细描述了这些。

2. Understand and use patterns and avoid antipatterns that affect the stability and capacity of your system. Michael Nygard’s excellent volume Release It! describes these in detail.

3. 让团队在所选架构的边界内工作,但除了在适当的地方应用模式之外,忽略优化容量的诱惑。鼓励在深奥的代码中清晰和简单。在没有明确的测试证明价值的情况下,永远不要为了容量而牺牲可读性。

3. Keep the team working within the boundaries of the chosen architecture but, other than applying patterns where appropriate, ignore the lure to optimize for capacity. Encourage clarity and simplicity in code over esoterica. Never compromise readability for capacity without an explicit test that demonstrates the value.

4. 注意选择的数据结构和算法,确保它们的属性适合你的应用。例如,如果您需要 O(1) 性能,请不要使用 O(n) 算法。

4. Pay attention to the data structures and algorithms chosen, making sure that their properties are suitable for your application. For example, don’t use an O(n) algorithm if you need O(1) performance.

5.穿线时要格外小心。Dave 当前的项目是他工作过的性能最高的系统——他的交易系统每秒可以处理数万笔交易——实现这一目标的关键方法之一是保持应用程序的核心是单线程的。作为尼加德说,“阻塞的线程反模式是大多数失败的直接原因……导致连锁反应和级联失败。”2个

5. Be extremely careful about threading. Dave’s current project is the highest performance system he has worked on—his trading system can process tens of thousands of transactions per second—and one of the key ways to achieve this was by keeping the core of the application single-threaded. As Nygard says, “The blocked threads antipattern is the proximate cause of most failures... leading to chain reactions and cascading failures.”2

6. 建立断言所需容量水平的自动化测试。当这些测试失败时,将它们用作解决问题的指南。

6. Establish automated tests that assert the desired level of capacity. When these tests fail, use them as a guide to fixing the problems.

7. 使用分析工具作为解决测试发现的问题的集中尝试,而不是作为一般的“尽快解决”策略。

7. Use profiling tools as a focused attempt to fix problems identified by tests, not as a general “make it as fast as possible” strategy.

8. 尽可能使用真实世界的能力衡量标准。您的生产系统是您唯一真正的测量来源。使用它并了解它在告诉您什么。特别注意系统的用户数量、他们的行为模式以及生产数据集的大小。

8. Wherever you can, use real-world capacity measures. Your production system is your only real source of measurement. Use it and understand what it is telling you. Pay particular attention to the number of users of the system, their patterns of behavior, and the size of the production data set.

测量容量

Measuring Capacity

衡量容量涉及调查应用程序的广泛特征。以下是一些可以执行的测量类型:

Measuring capacity involves investigating a broad spectrum of characteristics of an application. Here are some types of measurements that can be performed:

可扩展性测试当我们添加更多的服务器、服务或线程时,单个请求的响应时间和可能的并发用户数如何变化?

Scalability testing. How do the response time of an individual request and the number of possible simultaneous users change as we add more servers, services, or threads?

寿命测试这涉及长时间运行系统,以查看性能是否会随着长时间的运行而发生变化。这种类型的测试可以发现内存泄漏或稳定性问题。

Longevity testing. This involves running the system for a long time to see if the performance changes over a protracted period of operation. This type of testing can catch memory leaks or stability problems.

吞吐量测试系统每秒可以处理多少事务、消息或页面点击?

Throughput testing. How many transactions, or messages, or page hits per second can the system handle?

负载测试当应用程序的负载增加到类似生产的比例甚至更高时,容量会发生什么变化?这可能是最常见的容量测试类别。

Load testing. What happens to capacity when the load on the application increases to production-like proportions and beyond? This is perhaps the most common class of capacity testing.

所有这些都代表了对系统行为的有趣且有效的测量,但可能需要不同的方法。前两种测试与其他两种测试有根本的不同,因为它们意味着相对测量:当我们改变系统的属性时,系统的性能配置文件如何变化?但是,第二组仅可用作绝对度量。

All of these represent interesting and valid measurements of the behavior of the system, but can require different approaches. The first two types of testing are fundamentally different from the other two in that they imply relative measurements: How does the performance profile of the system change as we change attributes of the system? The second group, though, are only useful as absolute measures.

在我们看来,容量测试的一个重要方面是能够模拟给定应用程序的实际使用场景。这种方法的替代方法是对系统中的特定技术交互进行基准测试:“数据库每秒可以存储多少事务?”、“每秒有多少消息消息队列可以传送吗?”等等。虽然在项目中有时此类基准测量可能具有价值,但与更侧重于业务的问题(如“在给定常规使用模式的情况下,我每秒可以处理多少销售额?”)相比,它们是学术性的。或“我预测的用户群能否在高峰负载时有效地使用系统?”

In our view, an important aspect of capacity testing is the ability to simulate realistic use scenarios for a given application. The alternative to this approach is to benchmark specific technical interactions in the system: “How many transactions per second can the database store?”, “How many messages per second can the message queue convey?”, and so on. While there are times in a project when such benchmark measurements can be of value, they are academic when compared to the more business-focused questions like “How many sales per second can I handle, given regular usage patterns?” or “Can my predicted user base use the system effectively at times of peak load?”

集中的基准式容量测试对于防范代码中的特定问题和优化特定区域的代码非常有用。有时,它们可以提供有用的信息来帮助进行技术选择过程。然而,它们只是图片的一部分。如果性能或吞吐量是应用程序的重要问题,那么我们需要一些测试来断言系统满足其业务需求的能力,而不是我们作为技术人员对特定组件的吞吐量应该是多少的猜测。

Focused, benchmark-style capacity tests are extremely useful for guarding against specific problems in the code and optimizing code in a specific area. Sometimes, they can be useful by providing information to help with technology selection processes. However, they form only a part of the picture. If performance or throughput is an important issue for an application, then we need some tests that assert the system’s ability to meet its business needs, not our guess as technicians as to what the throughput of a particular component should be.

因此,我们认为将基于场景的测试纳入我们的容量测试策略至关重要。我们展示了一个特定的系统使用场景作为测试,并根据我们对它在现实世界中必须实现的业务预测进行评估。我们在第 238 页的“自动化容量测试”部分对此进行了更详细的描述

Because of this, we believe it is vital to include scenario-based testing into our capacity testing strategy. We represent a specific scenario of use of the system as a test, and evaluate that against our business predictions of what it must achieve in the real world. We describe this in more detail in the “Automating Capacity Testing” section on page 238.

然而,在现实世界中,大多数现代系统——至少是我们通常使用的类型——并不是一次只做一件事。销售点系统在处理销售的同时,也在更新库存、处理服务订单、记录时间表、支持店内审计等。如果我们的能力测试不测试如此复杂的交互组合,那么它们将无法防御许多类别的问题。这意味着我们的每个基于场景的测试都应该能够与涉及其他交互的其他容量测试一起运行。为了最有效,容量测试应该可组合到将并行运行的更大规模的套件中。

In the real world, though, most modern systems—at least the type that we generally work on—are not doing one thing at a time. While the point of a sale system is processing sales, it is also updating stock positions, handling orders for services, recording timesheets, supporting an in-store audit, and so on. If our capacity tests don’t test such complex combinations of interactions, there are many classes of problems that they will be unable to defend against. The implication of this is that each of our scenario-based tests should be capable of running alongside other capacity tests involving other interactions. To be most effective, capacity tests should be composable into larger-scale suites which will run in parallel.

正如 Nygard 所说,确定要应用的负载量和负载类型,并处理替代路径场景,例如未经授权的索引服务抓取您的系统,“既是一门艺术,也是一门科学。复制真实的生产流量是不可能的,因此您可以使用流量分析、经验和直觉来尽可能接近现实的模拟。”3个

Working out how much and what kind of load to apply, and taking care of alternative-path scenarios such as unauthorized indexing services scraping your system is, as Nygard says, “both an art and a science. It is impossible to duplicate real production traffic, so you use traffic analysis, experience, and intuition to achieve as close a simulation of reality as possible.”3

如何定义能力测试的成功与失败?

How Should Success and Failure Be Defined for Capacity Tests?

我们看到的许多容量测试肯定比测试更多的是测量。成功或失败通常取决于对收集到的测量值的人工分析。容量测量策略相对于容量测试策略的缺点是分析结果可能需要很长时间。然而,如果它还能够生成测量值,提供对发生的事情的洞察力,而不仅仅是失败或成功的二元报告,那么它是任何容量测试系统的一个非常有用的特性。一张图真的值一千能力测试背景下的词语——趋势对于决策制定与绝对值一样重要。出于这个原因,我们始终将创建图表作为容量测试的一部分,并确保可以从我们的部署管道仪表板轻松访问它们。

Much capacity testing that we have seen has definitely been more measurement than testing. Success or failure is often determined by a human analysis of the collected measurements. The drawback with a capacity measurement strategy over a capacity testing strategy is that it can be a lengthy exercise to analyze the results. However, it is an extremely useful property of any capacity test system if it is also able to generate measurements, providing insight into what happened, not just a binary report of failure or success. A graph is really worth a thousand words in the context of capacity testing—trends can be as important to decision making as absolute values. For this reason, we always create graphs as part of our capacity testing and make sure they are easily accessible from our deployment pipeline dashboard.

但是,如果我们将我们的容量环境用于测试和测量,那么对于我们运行的每个测试,我们需要定义它通过的意义。设置容量测试应该通过的级别是很棘手的。一方面,如果您将级别设置得太高,以至于您的应用程序只能在一切都对它有利时才能通过,您很可能会遭受定期的、间歇性的测试失败。当网络正在用于其他任务时,或者当您的容量测试环境同时处理另一项任务时,您的测试可能会失败。

However, if we are using our capacity environment for testing as well as measurement, then, for each test that we run, we need to define what it means for it to pass. Setting the level at which capacity tests should pass is tricky. On one hand, if you set the level too high, so that your application can only just pass when everything is in its favor, you are likely to suffer regular, intermittent test failures. Your tests may fail when the network is in use for other tasks, or when your capacity test environment is simultaneously working on another task.

相反,如果您的测试断言您的应用程序必须每秒处理 100 个事务 (tps),​​而它实际上可以处理 200 个事务,那么您的测试将不会发现引入几乎减半吞吐量的更改。这意味着您会将一个潜在的难题推迟到某个不可预知的以后时间,在您忘记有罪更改的细节很久之后。一段时间后,您可能出于充分的理由进行了一些无害的更改,减少了容量,即使只减少了百分之几,测试也会失败。

Conversely, if your test asserts that your application must handle 100 transactions per second (tps) while it can actually handle 200, then your test won’t spot introducing a change that almost halves the throughput. That means you will defer a potentially difficult problem to some unpredictable later time, long after you have forgotten the details of the guilty change. Some time later you might make an otherwise innocent change that reduces the capacity for a good reason, and the test fails even if the reduction was only a few percent.

这里有两种策略可以采用。首先,以稳定、可重复的结果为目标。尽可能将容量测试环境与其他影响隔离开来,并将它们专门用于测量容量的任务。这最大限度地减少了其他与测试无关的任务的影响,从而使结果更加一致。容量测试是虚拟化不适用的少数情况之一(除非您的生产环境是虚拟的),因为它会引入性能开销。接下来,通过在测试以最低可接受水平通过后逐步提高每个测试的通过阈值。这可以保护您免受误报情况的影响。如果测试在提交后开始失败,并且阈值设置远高于您的要求,

There are two strategies to adopt here. First, aim for stable, reproducible results. As far as practically possible, isolate capacity test environments from other influences and dedicate them to the task of measuring capacity. This minimizes the impact of other, non-test-related, tasks and so makes the results more consistent. Capacity testing is one of the few situations where virtualization is not appropriate (unless your production environment is virtual) because of the performance overhead it introduces. Next, tune the pass threshold for each test by ratcheting it up once the test is passing at a minimum acceptable level. This provides you with protection from the false-positive scenario. If the test begins to fail following a commit, and the threshold is set well above your requirement, then you can always decide to simply lower the threshold if the capacity degradation is for a well-understood and acceptable reason, but the test will retain its value as a protection against inadvertent capacity-damaging changes.

为了让我们的测试成为真正的测试,而不是性能测量,每个测试都必须包含一个特定的场景,并且必须根据测试被视为通过的阈值进行评估。

For our tests to be genuine tests, rather than performance measurements, each must embody a specific scenario and must evaluate against a threshold beyond which the test is deemed to pass.

容量测试环境

The Capacity-Testing Environment

理想情况下,系统容量的绝对测量应该在尽可能接近地复制系统最终运行的生产环境的环境中进行。

Absolute measurements of the capacity of a system should ideally be carried out in an environment that, as closely as possible, replicates the production environment in which the system will ultimately run.

虽然可以从不同配置的环境中学习有用的信息,除非它们基于测量,但从测试环境中的容量到生产环境中的容量的任何外推都是高度推测性的。高性能计算机系统的行为是一个专业且复杂的领域。配置更改往往会对容量特性产生非线性影响。改变允许的 UI 会话与应用程序服务器连接数和数据库连接数的比率等简单的事情可以将系统的整体吞吐量提高一个数量级(因此这些是一些重要的变量)。

While it is possible to learn useful information from differently configured environments, unless they are based on measurement, any extrapolation from capacity in the test environment to capacity in the production environment is highly speculative. The behavior of high-performance computer systems is a specialist and complex area. Configuration changes tend to have a nonlinear effect on capacity characteristics. Simple things like altering the ratio of permitted UI sessions to the number of application server connections and database connections can increase the overall throughput of a system by an order of magnitude (so these are some of the important variables to play with).

如果容量或性能对您的应用程序来说是一个严重的问题,请进行投资并为核心部分创建生产环境的克隆你的系统。使用相同的硬件和软件规范,并遵循我们关于如何管理配置的建议,以确保您为每个环境使用相同的配置,包括网络、中间件和操作系统配置。在大多数情况下,如果您正在构建一个高容量系统,除此之外的任何策略都是一种妥协,会带来额外的风险——当它在其生产环境中时,连接到真实的外部系统,并且具有真实的负载和生产规模数据集,您的应用程序将无法满足您的容量要求。

If capacity or performance is a serious issue for your application, make the investment and create a clone of your production environment for the core parts of your system. Use the same hardware and software specifications, and follow our advice on how to manage configuration to ensure that you are using the same configuration for each environment, including networking, middleware, and operating system configuration. In most circumstances, if you are building a highcapacity system, any strategy other than this is a compromise which comes with additional risk—that when it is in its production environment, connected to real external systems, and with a real load and production-sized data sets, your application will not be able to meet your capacity requirements.

在现实世界中,在生产环境的精确副本中进行容量测试的理想情况并不总是可行的。有时它甚至是不明智的,例如当项目足够小,或者当应用程序的性能不值得关注以保证复制生产硬件的费用时。

In the real world, the ideal of capacity testing in an exact replica of the production environment isn’t always possible. Sometimes it is not even sensible, for example when the project is small enough, or when the performance of the application is of insufficient concern to warrant the expense of duplicating production hardware.

生产的复制品同样不适合另一个极端的项目。大型软件即服务提供商通常会在其生产环境中运行成百上千台服务器,因此维持维护开销是不切实际的,更不用说完全复制其生产环境所需的硬件成本了。即使他们这样做了,生成负载以对此类环境和代表性数据集施加压力的复杂性也将是一个庞大的企业。在这种情况下,容量测试可以作为金丝雀发布策略的一部分执行(有关更多信息,请参阅第 263 页的“金丝雀发布”部分)。可以通过更频繁的发布来降低新更改改变应用程序容量的风险。

A replica of production is equally inappropriate for projects at the other extreme. Big software-as-a-service providers will often have hundreds or thousands of servers running in their production environments, so it is impractical to sustain the maintenance overhead, let alone hardware costs, that fully replicating their production environment would entail. Even if they did, the complexity of generating the load to stress such environments and a representative data set would be a mammoth enterprise. In situations like this, capacity testing can be performed as part of a canary release strategy (see the “Canary Releasing” section on page 263 for more on this). The risk of new changes altering the application’s capacity can be mitigated by more frequent releases.

但是,大多数项目介于这些极端之间,并且此类项目应尝试在与生产环境类似的环境中运行容量测试可能的。即使项目太小而无法保证复制生产环境的费用,您也应该记住,虽然在低规格硬件上进行容量测试会突出任何严重的容量问题,但它无法证明应用程序可以完全实现其目标。这是一个必须为项目评估的风险——但不要天真地进行计算。

Most projects, though, sit somewhere between these extremes, and such projects should try to run capacity tests in environments as similar to production as possible. Even if the project is too small to warrant the expense of replicating the production environment, you should remember that, while capacity testing on lower-specification hardware will highlight any serious capacity problems, it won’t be able to demonstrate that the application can fully meet its goals. This is a risk that must be evaluated for the project—but don’t be naive in your calculations.

不要指望您的应用程序会随着硬件的某些特定参数线性扩展而自欺欺人。例如,如果您的测试处理器的时钟速率是生产服务器的一半,那么假设应用程序在生产中的速度将是生产服务器的两倍是天真的想法。这不仅假设您的应用程序受 CPU 限制,而且随着 CPU 速度的提高,它仍然是瓶颈。复杂系统很少以这种线性方式运行,即使它们被设计为如此。

Don’t fool yourself by counting on your application scaling linearly with some particular parameter of your hardware. For example, it is naive to assume that an application will be twice as fast in production if your test processor has half the clock rate of the production servers. That assumes not only that your application is CPU-bound, but also that as CPU speed increases, it still remains the bottleneck. Complex systems very rarely behave in such a linear fashion, even when they are designed to do so.

如果您别无选择,请尝试进行多次扩展运行以对测试环境和生产环境之间的差异进行基准测试(如果可能的话)。

If you have no other choice, try to get a number of scaling runs to benchmark the variance between the test and production environments, if at all possible.

图 9.1 示例生产服务器场

Figure 9.1 Example production server farm

图片

图 9.2 示例容量测试环境

Figure 9.2 Example capacity test environment

图片

一个明显的策略可以限制测试环境成本并提供一些合理准确的性能度量,其中应用程序将部署到服务器场的生产环境中,如图 9.1所示。复制服务器的一部分,如图 9.2所示,而不是整个服务器场。

One obvious strategy to limit the test environment costs and to provide some sensibly accurate performance measures is available where the application is to be deployed into production on a farm of servers, as shown in Figure 9.1. Replicate one slice of the servers, as shown in Figure 9.2, not the whole farm.

例如,如果您的应用程序部署到四台 Web 服务器、八台应用程序服务器和四台数据库服务器,则在您的性能测试环境中各有一台 Web 和数据库服务器以及两台应用程序服务器。这将为您提供相当准确的单条腿性能测量值,并且它可能会揭示当两个或多个服务器争用来自另一层的资源(例如数据库连接)时出现的一些问题。

For example, if your application is deployed to four web servers, eight application servers, and four database servers, have one each of the web and database servers and two application servers in your performance test environment. This will give you fairly accurate measurements of performance for a single leg, and it may reveal some of the problems arising when two or more servers are in contention for resources from another tier, such as database connections.

外推能力是一种启发式方法,在如何最好地实践它以及它是否成功方面会因项目而异。我们只能建议您以健康的怀疑态度对待有关外推结果的假设。

Extrapolating capacity is a heuristic that will vary widely, both in how to best practice it and in whether it is successful, from project to project. We can only advise that you treat assumptions about extrapolating results with a healthy degree of skepticism.

自动化容量测试

Automating Capacity Testing

在过去的项目中,我们错误地将能力测试视为完全独立的活动:独立于交付过程的一个阶段。这种方法是对开发和运行这些测试的成本的反应。暂时忽略成本,当容量是项目的特定问题时,了解您引入了影响系统容量的更改与了解您引入了功能问题同样重要。在引入相应更改后,您需要尽快了解容量减少情况,以便快速有效地修复它。这主张将容量测试作为一个阶段添加到部署管道中。

On projects in the past, we have mistakenly treated capacity testing as a wholly separate exercise: a phase of the delivery process in its own right. This approach was a reaction to the cost of developing and running those tests. Ignoring costs for a moment, when capacity is a specific issue for a project, it is as important to know that you have introduced a change affecting the system’s capacity as it is to know that you have introduced a functional problem. You need to know about a reduction in capacity as soon as possible after the corresponding change was introduced, so you can fix it quickly and efficiently. This argues for adding capacity testing as a stage to the deployment pipeline.

如果我们要向管道添加容量测试,则应该创建一个自动容量测试套件,并针对通过提交阶段和(可选)验收测试阶段的系统的每个更改运行。这可能很困难,因为与其他类型的验收测试相比,容量测试可能是脆弱的、复杂的东西,很容易因软件的微小更改而中断——不是指示容量问题的有用中断,而是变更导致的中断在容量测试与之交互的界面中。

If we’re adding capacity testing to the pipeline, an automated capacity test suite should be created and run against every change to the system that passes the commit stage and (optionally) the acceptance test stage. This can be difficult because, even more than other types of acceptance tests, capacity tests may be fragile, complex things, easily broken with minor changes to the software—not with the useful breaks indicative of a capacity problem, but those resulting from a change in the interface that the capacity tests interact with.

容量测试应

Capacity tests should

• 测试特定的真实场景,这样我们就不会通过过于抽象的测试而遗漏真实世界使用中的重要错误

• Test specific real-world scenarios, so we don’t miss important bugs in real-world use through overly abstract testing

• 有一个预定义的成功门槛,这样我们就可以知道他们已经通过了

• Have a predefined threshold for success, so we can tell that they have passed

• 持续时间短,以便容量测试可以在合理的时间内进行

• Be of short duration, so that capacity testing can take place in a reasonable length of time

• 面对变化保持稳健,避免不断返工以跟上应用程序的变化

• Be robust in the face of change, to avoid constant rework to keep up with changes to the application

• 可组合成更大规模的复杂场景,以便我们可以模拟真实世界的使用模式

• Be composable into larger-scale complex scenarios, so that we can simulate real-world patterns of use

• 可重复,能够顺序和并行运行,这样我们既可以构建测试套件来应用负载,也可以运行寿命测试

• Be repeatable, capable of running sequentially and in parallel, so that we can both build suites of tests to apply load and run longevity tests

以不通过过度设计的测试削弱开发进度的方式实现所有这些目标并不容易。一个好的策略是采用一些现有的验收测试并将它们调整为容量测试。如果您的验收测试是有效的,它们将代表与您的系统交互的真实场景,并且在面对应用程序的变化时将是健壮的。它们缺乏的特性是:扩展能力,以便您可以对应用程序施加严重的负载,以及衡量成功的规范。

Achieving all of these goals in a manner that does not cripple development progress with over-engineered testing is not easy. A good strategy is to take some existing acceptance tests and adapt them to become capacity tests. If your acceptance tests are effective, they will represent realistic scenarios of interaction with your system, and will be robust in the face of change in the application. The properties that they lack are: the ability to scale up so you can apply serious load to the application, and a specification of a measure of success.

在大多数其他方面,我们在前几章中给出的有关编写和管理有效验收测试的建议意味着它们将显着程度,已经满足了上面列出的关于良好容量测试的大部分标准。我们的目标有两个:创建一个逼真的生产类负载,以及选择和实施代表现实但病态的现实负载情况的场景。最后一点至关重要:我们不只是在验收测试中测试快乐路径,容量测试也是如此。例如,Nygard 建议了一种用于测试系统扩展性的有用技术:“确定最昂贵的交易是什么,并将这些交易的比例增加一倍或三倍。”4个

In most other respects, the advice that we have given in previous chapters about writing and managing effective acceptance tests means that they will, to a significant degree, already fulfill most of the criteria outlined above for good capacity tests. Our goal is two-fold: creating a realistic production-like load, and choosing and implementing scenarios that represent realistic but pathological real-life loading situations. The last point is essential: We don’t just test the happy path in acceptance tests, and the same is true of capacity testing. For example, one useful technique for testing how your system scales is suggested by Nygard: “Identify whatever your most expensive transactions are, and double or triple the proportion of those transactions.”4

如果您可以记录这些测试与系统执行的交互,多次复制它们,然后重放这些副本,您就可以对被测系统施加各种负载,从而测试各种场景。

If you can record the interactions that these tests perform with the system, duplicate them many times over, and then replay the duplicates, you can apply various kinds of load to the system under test and thus test various scenarios.

我们已经看到这种总体策略在几个项目中发挥了作用,每个项目都使用非常不同的技术并且每个项目都有非常不同的容量测试需求。测试信息本身的记录方式、放大方式以及回放方式的细节在不同项目之间差异很大。一致的是记录功能验收测试的输出,对其进行后处理以扩大请求,为每个测试添加成功标准,然后重放测试以应用与系统的大量交互的基本原理。

We have seen this general strategy work on several projects, each using very different technologies and each having very different capacity testing needs. The details of how the information for the tests themselves was recorded, how it was scaled up, and how it was replayed, varied enormously between the projects. What was consistent were the fundamentals of recording the output of functional acceptance tests, postprocessing it to scale up the requests, adding success criteria for each test, and then replaying the tests to apply very high volumes of interaction with the system.

要做出的第一个战略决策是在应用程序中的哪个点进行录制,以及随后的回放。我们的目标是尽可能地模拟系统的实际使用;然而,这样做是有代价的。对于某些系统,只需记录通过用户界面执行的交互并回放就足够了。但是,如果您正在开发一个供数万或更多用户使用的系统,请不要尝试通过 UI 交互来向系统施加负载。为了使它成为一个真实的模拟,您需要数以千计的客户端机器专用于向系统注入负载的任务。有时必须做出妥协。

The first strategic decision to be made is at which point in the application should recording, and the subsequent playback, take place. Our goal is to simulate realistic use of the system as closely as we can; however, there are costs to this. For some systems, simply recording interactions performed via the user interface and playing them back will be sufficient. However, if you are developing a system to be used by tens of thousands of users or more, do not attempt to apply load to the system by interacting through the UI. For this to be a realistic simulation, you would need thousands of client machines dedicated to the task of injecting load to the system. Compromises must sometimes be made.

使用现代面向服务的体系结构构建的系统,或那些使用异步通信作为主要输入的系统,特别适合我们的一种常见策略:记录和回放。

Systems built using modern service-oriented architectures, or those using asynchronous communications as primary inputs, are particularly amenable to one of our common strategies: record and playback.

图 9.3 容量测试的潜在注入点

Figure 9.3 Potential injection points for capacity testing

图片

根据系统行为的许多变量及其基本架构,选择归结为在几个点上进行记录和回放(图 9.3):

Depending on a lot of variables of the system’s behavior and its fundamental architecture, the choices boil down to recording and playing back at several points (Figure 9.3):

1. 通过用户界面。

1. Through the user interface.

2. 通过服务或公共 API——例如,直接向 Web 服务器发出 HTTP 请求。

2. Through a service or public API—for example, making HTTP requests directly into a web server.

3. 通过较低级别的 API——例如,直接调用服务层或数据库。

3. Through a lower-level API—for example, making direct calls to a service layer or perhaps the database.

通过用户界面进行容量测试

Capacity Testing via the User Interface

记录和随后回放与系统交互的最明显点是通过用户界面。这是大多数商业负载测试产品运行的点。这些工具提供了通过用户界面编写或直接记录交互的能力,然后复制和扩展这些测试交互,以便测试可以为每个测试用例模拟数百或数千个交互。

The most obvious point at which to record and subsequently play back interactions with the system is via the user interface. This is the point at which most commercial load-testing products operate. Such tools provide the ability to either script or directly record interactions via the user interface, and then to duplicate and scale up these test interactions so that the test can simulate hundreds or thousands of interactions for each test case.

正如我们已经提到的,对于大容量系统,这并不总是一种实用的方法,尽管充分运行系统具有显着优势。这种方法还有另一个明显的缺点:在分布式架构中,服务器托管重要的业务逻辑,因此容量问题更可能集中在服务器上,可能无法向系统施加足够的负载来对其进行适当的测试。当客户端过于复杂,具有自己的重要逻辑或过于单薄(例如用于集中式服务的轻量级 UI)时,对于此类系统来说可能是这样。在这些情况下,真正的衡量标准是客户端与服务器的比率。

As we have already mentioned, for high-volume systems this is not always a practical approach, despite the significant advantage of fully exercising the system. Such an approach has another significant disadvantage: In distributed architectures, where servers host significant business logic—and thus are where capacity problems are more likely to be concentrated—it may not be possible to apply sufficient load to the system to test it appropriately. This can be true for such systems when the clients are either too complex, with significant logic of their own, or too thin, such as lightweight UIs for the centralized services. In these cases, the real measure is the ratio of clients to servers.

对于某些系统,基于 UI 的测试是正确的做法,但实际上,它仅适用于处理中等容量的系统。即使那样,管理和维护以 UI 为中心的测试的成本也可能非常高。

For some systems, UI-based testing is the right thing to do, but realistically, it is an appropriate strategy only for systems that handle moderate volumes. Even then, the cost of managing and maintaining the UI-centered tests can be very high.

基于 UI 的测试存在一个基本问题。任何设计良好的系统都将包含关注不同问题的组件。在大多数应用程序中,根据定义,UI 的作用是提供适合系统用户与之交互的界面。该界面通常采用广泛的交互集,并将它们浓缩为与系统其他组件的更有针对性的交互:例如,一系列文本输入、列表选择、按钮点击等通常会导致一个事件传递给另一个成分。第二个组件将具有更稳定的 API,因此针对此 API 运行的测试将比编写到 GUI 的测试更不脆弱。

There is a fundamental problem at play with UI-based testing. Any well-designed system will comprise components that focus on different concerns. In most applications, the role of the UI is to provide, by definition, an interface appropriate for the user of the system to interact with it. This interface usually takes a broad set of interactions and condenses them into more targeted interactions with other components of the system: For example, a sequence of text entries, list selections, button clicks, and so forth often results in a single event passed to another component. This second component will have a more stable API, and the tests that run against this API will therefore be less fragile than those written to a GUI.

对于分布式应用程序中的容量测试,我们是否对 UI 客户端的性能感兴趣取决于系统的性质。为了简单、瘦的基于 Web 的客户端,我们通常对客户端本身的性能更感兴趣,而不是对话服务器端的集中资源的性能。如果我们的验收测试是为了测试 UI 并确保通过它进行的交互以功能正确的方式运行,那么在应用程序的后期记录点之一进行容量测试可能是一个更有效的选择。然而,相反,一些容量问题仅表现为客户端和服务器之间的交互,尤其是在胖客户端的情况下。

For capacity testing in a distributed application, whether or not we are interested in the performance of UI clients depends on the nature of the system. For simple, thin web-based clients, we are often less interested in the performance of the client itself than that of the centralized resources at the server end of the conversation. If our acceptance tests were written to exercise the UI and ensure that interactions through it operate in a functionally correct manner, recording at one of the later points in the application for capacity test purposes may be a more effective option. Conversely, however, some capacity problems only manifest themselves as interactions between clients and a server, especially in the case of thick clients.

对于具有复杂客户端应用程序和基于服务器的集中式组件的分布式系统,分离容量测试、找到中间记录和注入点(如我们之前所述)以测试服务器并定义独立的 UI 客户端测试通常是有意义的UI 针对后端系统的存根版本运行。我们认识到这个建议违背了我们之前的建议,即使用端到端的“整个系统”测试进行容量测试,但我们认为在容量测试分布式系统中的 UI 是一种特殊情况,最好这样处理。在这种情况下,它比其他情况更取决于被测系统的性质。

For distributed systems with complex client applications and centralized server-based components, it often makes sense to separate out capacity testing, finding an intermediate record and injection point, as we described earlier, to test the servers, and defining independent UI client tests where the UI operates against a stubbed version of the back-end system. We recognize that this advice goes against our earlier recommendation to use end-to-end “whole system” tests for capacity testing, but we consider the UI, in capacity-testing distributed systems, to be a special case, best treated as such. In this instance, more than others, it depends on the nature of the system under test.

总而言之,尽管这是最常见的容量测试方法,当然也体现在现成的容量测试产品中,但我们通常更愿意避免通过 UI 进行容量测试。例外情况是证明 UI 本身或客户端与服务器之间的交互不是性能瓶颈很重要。

To summarize, although it is the most common approach to capacity testing, certainly as embodied in off-the-shelf capacity test products, we generally prefer to avoid capacity testing through the UI. The exception is when it is important to prove that the UI itself, or alternatively the interactions between clients and a server, are not a performance bottleneck.

记录针对服务或公共 API 的交互

Recording Interactions against a Service or Public API

此策略可用于提供除图形用户界面之外的公共 API 的应用程序,例如 Web 服务、消息队列或其他一些事件驱动的通信机制。这可能是记录交互的理想点,让您可以避开客户端横向扩展的问题、管理成百上千个客户端进程的复杂性以及通过用户界面与系统交互的相对脆弱性。面向服务的体系结构特别适合这种方法。

This strategy can be used in applications that provide a public API other than a graphical user interface, such as a web service, message queues, or some other event-driven communication mechanism. This can be an ideal point to record interactions, allowing you to sidestep the issues of client scale-out, the complexity of managing hundreds or thousands of client processes, and the relative fragility of interacting with the system via the user interface. Service-oriented architectures are particularly suited to this approach.

图 9.4 记录针对公共 API 的交互

Figure 9.4 Recording interactions against a public API

图片

图 9.4显示了容量测试记录器组件的图表,该组件在交互发生时进行记录。

Figure 9.4 shows a diagram of a capacity-test recorder component making a record of the interactions as they happen.

使用录制的交互模板

Using Recorded Interaction Templates

我们首先记录交互的目的是实现一种与验收测试所体现的系统交互的模板。这些交互模板稍后将用于为后续的容量测试运行生成容量测试数据。

Our objective in recording interactions in the first place is to achieve a kind of template of the interactions with the system that an acceptance test embodies. These interaction templates will be used later to generate the capacity test data for a subsequent capacity test run.

我们的理想是执行一次特殊的验收测试运行,或者其中的一个子集代表容量测试场景。在此特殊运行期间,我们将通过注入一段额外的代码以某种方式检测代码,该代码将记录交互,将它们保存到磁盘,并将它们转发到适当的系统。从系统的其余部分的角度来看,发生的交互没有区别——记录是透明的:我们只是将所有输入和输出的副本转移到磁盘上。

Our ideal is to perform a special run of the acceptance tests, or a subset of them representing capacity test scenarios. During this special run, we will instrument the code in some manner by injecting an additional piece of code that will record the interactions, save them to disk, and forward them to the system proper. From the rest of the system’s perspective, there is no difference in the interactions that take place—the recording is transparent: We simply divert a copy of all inputs and outputs to disk.

图 9.5 创建交互模板

Figure 9.5 Creating interaction templates

图片

图 9.5显示了此过程的一个简单示例的图表。在这个例子中,一些值被标记为将来替换,一些被保留,因为它们不影响测试的意义。显然,可以根据需要在消息中添加或多或少的标记。不过,总的来说,我们倾向于更少而不是更多的替代品;我们的目标应该是尽可能少地更换。这将限制测试和测试数据之间的耦合,从而使我们的测试更加灵活和不那么脆弱。

Figure 9.5 shows a diagram of a simple example of this process. In this example, some values are tagged for future replacement and some are left alone, since they don’t affect the meaning of the test. Clearly, as much or as little tagging within the message can be done as needed. On the whole, though, we tend toward less rather than more replacement; we should aim to replace as little as we can get away with. This will limit the coupling between the test and the test data, thus making our tests more flexible and less fragile.

图 9.6 从交互模板创建测试实例

Figure 9.6 Creating test instances from interaction templates

图片

一旦交互模板被记录下来,我们将创建测试数据来伴随它们。生成此数据是为了补充交互模板,当与适当的模板组合时,每个测试数据集合代表与被测系统交互的有效实例。图 9.6显示了表示流程中此步骤的图表。

Once the interaction templates have been recorded, we will create the test data to accompany them. This data is generated to complement the interaction templates, with each collection of test data representing, when combined with an appropriate template, a valid instance of interaction with the system under test. Figure 9.6 shows a diagram representing this step in the process.

除了模板的记录内容外,我们还为模板所代表的测试添加了一个成功标准。我们还没有足够的测试经验来推荐它作为最好和唯一的方法;到目前为止,我们只在一个项目上尝试过,但对于那个项目来说,这是非常成功的,帮助我们创建了一个非常简单但非常强大的容量测试系统。一旦建立,这个系统只需很少的努力来记录新的测试,并且根本不需要任何努力来准备和执行容量测试运行。

In addition to the recorded content of the template, we add a success criteria for the test that the template represents. We haven’t got enough experience of testing this way yet to recommend it as the best and only way; we have only tried it on one project so far, but for that project this was very successful and helped us to create a very simple yet very powerful capacity test system. Once established, this system took a very small effort to record new tests and no effort at all to prepare for and perform capacity test runs.

最后,当执行容量测试时,单独的测试实例会同时反馈到系统中。

Finally, when it is time to execute the capacity test, the separate test instances are fed back into the system at the same point.

交互模板和测试数据也可以作为开源性能测试工具的输入,例如 Apache 的 JMeter、Marathon 或 Bench。也可以编写一个简单的测试工具来以这种方式管理和运行测试。构建自己的容量测试工具并不像听起来那么愚蠢或困难;它允许定制容量测试工具以精确测量您的项目所需的内容。

Interaction templates and test data can also be used as inputs to open source performance test tools, such as Apache’s JMeter, Marathon, or Bench. It is also possible to write a simple test harness to manage and run tests in this way. Building your own capacity test harness is neither as silly nor as difficult as it may sound; it allows the capacity test harness to be tailored to measure precisely what you need for your project.

我们对本节中提出的建议有一个警告。对于非常高容量和高性能的系统,整个系统的最高性能部分必然是测试,而不是生产代码。测试必须运行速度足以施加负载并确认结果。现代硬件速度如此之快,以至于我们所谈论的性能水平非常不寻常,但如果您降低到计算时钟周期和调整编译器创建的机器代码的水平,交互模板的成本就太高了。至少,我们还没有找到让他们足够高效地测试应用程序的方法。

We have one caveat to the advice presented in this section. For seriously highcapacity and high-performance systems, the highest performance part of the whole system is, necessarily, the test, not the production code. The test has to operate fast enough to apply load and confirm results. Modern hardware is so fast that the levels of performance that we are talking about are extremely unusual, but if you are down to the level of counting clock cycles and tweaking the machine code that the compilers are creating, interaction templates are just too costly. At least, we haven’t yet found a way for them to be efficient enough to test the application.

使用容量测试存根开发测试

Using Capacity Test Stubs to Develop Tests

对于非常高性能系统中的容量测试,编写容量测试的复杂性通常会超过编写足够快的代码以通过它的复杂性。因此,必须断言测试可以以断言通过所需的速率进行测试。每当您编写容量测试时,重要的是首先要对被测应用程序、接口或技术实施一个简单的无操作存根,这样您就可以证明您的测试可以以所需的速度运行并正确断言当另一端不工作时通过。

For capacity testing in very high-performance systems, the complexity of writing the capacity test can often outweigh the complexity of writing code that is fast enough to pass it. It is therefore essential to assert that the test can test at the rates necessary to assert a pass. Whenever you are writing capacity tests, it is important to start by implementing a simple no-op stub of the application, interface, or technology under test so you can show that your test can run at the speeds that it needs to and correctly assert a pass when the other end is doing no work.

这听起来有点矫枉过正,但我​​们向您保证,我们已经看到许多容量测试断言应用程序失败,而实际上是测试本身无法跟上。在撰写本文时,Dave 正在一个非常高性能的计算环境中工作。在该项目中,我们在各个级别进行了一系列容量和性能测试。正如您所期望的那样,这些测试作为我们部署管道的一部分运行,并且它们中的大多数都有一个基准运行,该基准运行首先针对测试存根执行每个测试,在我们相信其结果之前断言测试本身是有效的。这些基准运行的结果与我们的其他容量测试结果一起报告,因此我们可以清楚地指出任何故障所在。

This may sound like overkill, but we assure you that we have seen many capacity tests asserting that the application fails when in fact it was the test itself that couldn’t keep up. Dave is, at the time of writing, working in a very highperformance computing environment. In that project, we run a battery of capacity and performance tests at all levels. These tests run as part of our deployment pipeline, as you would expect, and most of them have a benchmark run that first executes each test against a test stub, asserting that the test itself is valid before we trust its results. The results of these benchmark runs are reported alongside our other capacity test results, so we have a clear indication where any failure lies.

将容量测试添加到部署管道

Adding Capacity Tests to the Deployment Pipeline

大多数应用程序需要满足一些最小容量阈值。大多数现代商业应用程序将为许多并发用户提供服务,因此需要进行扩展以满足其峰值需求,同时提供可接受的性能。在开发过程中,我们需要的是能够断言我们的应用程序将达到客户要求的容量。

Most applications need to meet some minimum capacity threshold. Most modern commercial applications will be servicing many concurrent users, and will therefore be required to scale to meet their peak demand profile while delivering acceptable performance. During development, what we need is the ability to assert that our application will achieve the capacity required by the customer.

虽然与容量相关的非功能性需求是项目开发的一个重要方面,但重要的是要在某种可量化的衡量标准中指定“足够好”的含义。这些措施应该通过作为部署管道的一部分运行的某种自动化测试来评估。这意味着通过提交测试和验收测试的每个更改都应该对其运行自动容量测试。因此,可以确定引入显着影响应用程序容量的更改的时刻。

While capacity-related nonfunctional requirements are an important facet of the development of a project, it is important to specify what “good enough” means in some quantifiable measure. These measures should be evaluated by automated tests of some kind that are run as part of the deployment pipeline. That means that every change that passes the commit tests and acceptance tests should have automated capacity tests run against it. Thus it becomes possible to identify the moment of introducing a change that significantly affects the application’s capacity.

通过自动容量测试,并通过其成功标准明确划定最佳点,确保满足容量要求。这样,我们提防针对容量问题的过度设计解决方案。正如 YAGNI(“你不需要它”)原则所暗示的那样,我们始终遵循这样的格言:我们将做最少的工作来实现我们的目标。YAGNI 提醒我们,我们为防御添加的任何行为都可能是浪费精力。应用 Knuth 的格言,优化应该推迟到明确需要它们时,推迟到最后负责的时刻,并根据运行时应用程序分析确定目标,以便按重要性降序攻击瓶颈。

Passing the automated capacity tests, with the sweet spot clearly delineated by their success criteria, ensures that the capacity requirements are met. In this way, we guard against over-engineered solutions to the capacity problem. We always apply the dictum that we will do the minimum amount of work to achieve the result we are aiming for, as implied by the YAGNI (“You Ain’t ’Gonna Need It”) principle. YAGNI reminds us that any behavior we add defensively is potentially wasted effort. Applying Knuth’s dictum, optimizations should be deferred to the point when it is clear that they are required, deferred until the last responsible moment, and targeted based on runtime application profiling so as to attack bottlenecks in descending order of importance.

与以往一样,我们对任何测试的目标是在引入打破我们假设的更改后尽快失败。通过这种方式,可以轻松识别并快速修复更改。但是,容量测试通常相对复杂,并且可能需要很长时间才能运行。

As ever, our goal with any testing is to fail as quickly as possible after a change breaking our assumptions is introduced. In this way, the change is easily identified and quickly fixed. However, capacity tests are often relatively complex and can take a long time to run.

如果您足够幸运能够在几秒钟内证明您的应用程序满足其性能目标,请将您的容量测试添加到提交测试阶段,以便您可以立即获得有关任何问题的反馈。但是,在这种情况下,请注意任何依赖运行时优化编译器的技术。.NET 和 Java 中的运行时优化需要多次迭代才能稳定下来,并且只有在几分钟的“预热”后才能收集到合理的结果。

If you are lucky enough to be able to prove that your application meets its performance goals within a few seconds, add your capacity tests to the commit testing stage so you can get immediate feedback on any problems. However, in this case beware of any technology that relies on runtime optimizing compilers. The runtime optimizations in .NET and Java take many iterations to stabilize, and sensible results can only be gathered after several minutes of “warm-up.”

类似的策略可用于保护已知的性能热点不会随着代码的开发而变得更糟。当识别出这样的热点时,创建一个“保护测试”,作为提交测试周期的一部分运行得非常快。此类测试类似于一种性能冒烟测试——它们不会告诉您您的应用程序满足其所有性能标准,但它们可能会突出显示错误方向的趋势,并让您在它们成为问题之前解决它们。但是,请注意不要引入使用此策略间歇性失败的不可信测试。

A similar strategy can be useful for protecting known performance hot spots from getting worse over time as the code develops. When such a hot spot is identified, create a “guard test” that runs very quickly as part of you commit test cycle. Such tests act as a kind of performance smoke test—they aren’t going to tell you that your application meets all of its performance criteria, but they may highlight trends in the wrong direction and let you tackle them before they become a problem. However, watch out that you don’t introduce untrustworthy tests that fail intermittently with this strategy.

但是,大多数容量测试都不是部署管道提交阶段的候选者。它们通常需要很长时间并且需要太多资源才能运行。如果容量测试保持相当简单并且运行时间不会太长,则将容量测试添加到验收阶段是可行的。不过,总的来说,我们不建议将容量测试添加到部署管道的验收测试阶段。有几个原因:

Most capacity tests, though, aren’t candidates for the commit stage of your deployment pipeline. They usually take too long and require too many resources to run. Adding capacity tests to the acceptance stage is feasible if the capacity tests remain fairly simple and don’t take too long to run. On the whole, though, we don’t recommend adding capacity tests to the acceptance test stage of your deployment pipeline. There are several reasons:

• 要真正有效,容量测试需要在它们自己的特殊环境中运行。如果真正的原因是其他一些自动化测试在同一环境中同时运行,那么试图弄清楚为什么最新的候选版本如此严重地未能达到其容量要求可能会非常昂贵。一些 CI 系统允许您指定测试的目标环境。您可以使用此功能来划分容量测试并将它们与验收测试并行运行。

• To be really effective, capacity tests need to be run in their own special environment. Trying to figure out why the latest release candidate failed its capacity requirements so badly can be quite costly if the real reason was that some other automated tests were running simultaneously on the same environment. Some CI systems allow you to specify target environments for tests. You can use this feature to partition capacity tests and run them in parallel with acceptance tests.

• 某些类型的容量测试可能需要很长时间才能运行,导致在获得验收测试结果之前出现无法维持的延迟。

• Some types of capacity test can take a very long time to run, resulting in an untenable delay before getting an acceptance test result.

• 验收测试下游的许多活动可以与容量测试并行进行,例如演示最新的工作软件、手动测试、集成测试等。在成功的容量测试运行中控制这些是不必要的,而且对于许多项目来说,效率很低。

• Many activities downstream from acceptance testing can be done in parallel with capacity testing, such as demonstrating the latest working software, manual testing, integration testing, and so forth. Gating these on a successful capacity test run is unnecessary and, for many projects, inefficient.

• 对于某些项目,像验收测试一样频繁地运行容量测试没有意义。

• For some projects, it does not make sense to run capacity tests as frequently as acceptance tests.

一般来说,除了我们描述的性能冒烟测试之外,我们更愿意将自动化容量测试添加为部署管道中的一个完全独立的阶段。

In general, apart from the performance smoke tests we have described, we prefer to add automated capacity testing as a wholly separate stage in our deployment pipeline.

如何处理管道的这个容量阶段因项目而异。对于某些项目,以类似于验收测试阶段的方式对待它是有意义的——作为一个完全自动化的部署门。也就是说,除非容量测试阶段的测试全部通过,否则您无法在没有手动覆盖的情况下部署应用程序。这最适合高性能或大型应用程序,如果它们不满足众所周知的容量阈值,则根本不适合用途。这是最严格的容量测试模型,从表面上看,它似乎对大多数项目都是最佳的。然而,这并非总是如此。

How this capacity stage of the pipeline is treated differs somewhat from project to project. For some projects, it makes sense to treat it in a way similar to the acceptance test stage—as a fully automated deployment gate. That is, unless the tests in the capacity test stage all pass, you can’t deploy the application without a manual override. This is most appropriate for high-performance or large-scale applications that are simply not fit for purpose if they do not meet a well-understood threshold of capacity. This is the most rigorous model for capacity testing that, on the face of it, seems optimal to most projects. However, this is not always the case.

如果存在吞吐量或延迟的实际问题,或者仅在特定时间窗口相关或准确的信息,则自动化测试可以非常有效地充当可执行规范,可以断言满足要求。

If there are real issues of throughput or latency, or information that is only relevant or accurate for specific windows of time, automated tests can act very effectively as executable specifications that can assert that the requirement is met.

图 9.7 部署流水线容量测试阶段

Figure 9.7 The capacity test stage of the deployment pipeline

图片

在高层次上,部署管道中的验收测试阶段是所有后续测试阶段的模板,包括容量测试,如图 9.7所示。对于容量测试,与其他测试一样,该阶段从准备部署、部署开始,然后验证环境和应用程序是否已正确配置和部署。只有这样才能运行容量测试。

At a high level, the acceptance test stage in the deployment pipeline is a template for all subsequent testing stages, including capacity testing, as shown in Figure 9.7. For capacity tests, as for others, the stage begins by preparing for deployment, deploying, then verifying that the environment and application are correctly configured and deployed. Only then are the capacity tests run.

容量测试系统的其他好处

Additional Benefits of a Capacity Test System

容量测试系统通常与您预期的生产系统最接近。因此,它是一种非常宝贵的资源。此外,如果您遵循我们的建议并将您的容量测试设计为一系列可组合的、基于场景的测试,那么您真正拥有的是对您的生产系统的复杂模拟。

The capacity test system is usually the closest analog to your expected production system. As such, it is a very valuable resource. Further, if you follow our advice and design your capacity tests as a series of composable, scenario-based tests, what you really have is a sophisticated simulation of your production system.

出于各种原因,这是一种宝贵的资源。我们已经讨论了为什么基于场景的能力测试很重要,但考虑到对特定的、以技术为中心的交互进行基准测试的更常见的方法,值得重申一下。基于场景的测试提供了与系统的真实交互的模拟。通过将这些场景的集合组织成复杂的组合,您可以在类似生产的系统中使用尽可能多的诊断仪器有效地进行实验。

This is an invaluable resource for a whole variety of reasons. We have discussed already why scenario-based capacity testing is of importance, but given the much more common approach of benchmarking specific, technically focused interactions, it is worth reiterating. Scenario-based testing provides a simulation of real interactions with the system. By organizing collections of these scenarios into complex composites, you can effectively carry out experiments with as much diagnostic instrumentation as you wish in a production-like system.

我们已经使用此工具来帮助我们执行各种活动:

We have used this facility to help us perform a wide variety of activities:

• 再现复杂的生产缺陷

• Reproducing complex production defects

• 检测和调试内存泄漏

• Detecting and debugging memory leaks

• 寿命测试

• Longevity testing

• 评估垃圾收集的影响

• Evaluating the impact of garbage collection

• 调整垃圾回收

• Tuning garbage collection

• 调整应用程序配置参数

• Tuning application configuration parameters

• 调整第三方应用程序配置,例如操作系统、应用程序服务器和数据库配置

• Tuning third-party application configuration, such as operating system, application server, and database configuration

• 模拟病态的、最糟糕的一天情景

• Simulating pathological, worst-day scenarios

• 评估复杂问题的不同解决方案

• Evaluating different solutions to complex problems

• 模拟集成失败

• Simulating integration failures

• 通过一系列不同硬件配置的运行来衡量应用程序的可扩展性

• Measuring the scalability of the application over a series of runs with different hardware configurations

• 与外部系统进行负载测试通信,即使我们的容量测试最初是针对存根接口运行的

• Load-testing communications with external systems, even though our capacity tests were originally intended to run against stubbed interfaces

• 演练复杂部署的回滚。

• Rehearsing rollback from complex deployments.

• 有选择地失败的部分或应用程序来评估服务的优雅降级

• Selectively failing parts or the application to evaluate graceful degradation of service

• 在临时可用的生产硬件中执行实际容量基准测试,以便我们可以为长期、较低规格的容量测试环境计算更准确的比例因子

• Performing real-world capacity benchmarks in temporarily available production hardware so that we could calculate more accurate scaling factors for a longer-term, lower-specification capacity test environment

这不是一个完整的列表,但每个场景都来自一个真实的项目。

This is not a complete list, but each of these scenarios comes from a real project.

从根本上说,您的容量测试系统是一种实验资源,您可以在其中有效地加快或减慢时间以满足您的需要。您可以使用它来设计和执行各种实验以帮助诊断问题,或者预测问题并制定应对策略。

Fundamentally, your capacity test system is an experimental resource in which you can effectively speed up or slow down time to suit your needs. You can use it to design and execute all manner of experiments to help diagnose problems, or to predict issues and work out strategies to cope with them.

概括

Summary

设计系统以满足其非功能性需求是一个复杂的主题。许多 NFR 的交叉性质意味着很难管理它们对任何给定项目构成的风险。这反过来又会导致两种瘫痪行为:从项目开始就没有对它们给予足够的重视,或者在另一个极端,防御性架构和过度工程。

Designing systems to meet their nonfunctional requirements is a complex topic. The crosscutting nature of many NFRs means that it is hard to manage the risks that they pose to any given project. This, in turn, can lead to two paralyzing behaviors: not paying enough attention to them from the start of the project or, at the other extreme, defensive architecture and over-engineering.

技术人员被引诱到完整的、封闭的解决方案——也就是说,对于他们能想象到的尽可能多的情况完全自动化的解决方案。对于他们来说,这通常是解决问题的默认方法。例如,运营人员希望系统可以在不关闭的情况下重新部署和重新配置,而开发人员则希望保护自己免受应用程序未来每一次可能的演变,无论是否需要。NFR 是一个困难的领域,因为与功能需求相比,它们迫使技术人员为他们的分析提供更多输入,这可能会降低他们被要求交付的业务价值。

Technical people are lured towards complete, closed solutions—that is, solutions that are fully automated for as many cases as they can imagine. For them, this is usually the default approach to solving a problem. For example, operations people will want systems that can be redeployed and reconfigured without shutting down, whereas developers will want to defend themselves against every possible future evolution of the application, whether or not it will ever be required. NFRs are a difficult area because, compared to functional requirements, they forse technical people to provide more input into their analysis, which may detract them from the business value they are asked to deliver.

非功能性需求相当于桥梁建造者的软件,确保所选的梁足够坚固以应对预期的交通和天气。这些要求是真实的,必须加以考虑,但他们并不是为这座桥买单的商人的想法:他们想要的东西可以让他们从河的一侧到达另一侧,而且看起来不错. 这意味着,作为技术人员,我们必须谨防自己先看到技术解决方案的倾向。我们必须与客户和用户密切合作,以确定我们应用程序的敏感点,并根据实际业务价值定义详细的非功能性需求。

Nonfunctional requirements are the software equivalent of a bridge builder making sure that the chosen beams are strong enough to cope with the expected traffic and weather. These requirements are real, they have to be considered, but they aren’t what is in the mind of the business people paying for the bridge: They want something that can get them from one side of the river to the other, and looks nice. This means that, as technical people, we must guard carefully against our own tendency to see technical solutions first. We must work closely with customers and users to determine the sensitivity points of our application and define detailed nonfunctional requirements based upon real business value.

完成这项工作后,交付团队可以决定用于应用程序的正确架构,并创建需求和验收标准,以与捕获功能需求相同的方式捕获非功能需求。因此,可以估计满足非功能性需求所涉及的工作量,并以与功能性需求相同的方式对它们进行优先级排序。

Once this work has been done, the delivery team can decide upon the correct architecture to use for the application and create requirements and acceptance criteria capturing the nonfunctional requirements in the same way that functional requirements are captured. It thus becomes possible to estimate the effort involved in meeting nonfunctional requirements and prioritize them in the same way as functional requirements.

完成这项工作后,交付团队需要创建和维护自动化测试以确保满足这些要求。每次对应用程序、基础架构或配置的更改通过提交测试和验收测试阶段时,这些测试都应作为部署管道的一部分运行。将您的验收测试作为更广泛的基于场景的 NFR 测试的起点——这是获得全面、可维护的系统特性覆盖的好策略。

Once this work is done, the delivery team needs to create and maintain automated tests to ensure that these requirements are met. These tests should be run as part of your deployment pipeline every time a change to your application, infrastructure, or configuration passes the commit test and acceptance test stages. Use your acceptance tests as a starting point for broader scenario-based testing of NFRs—this is a great strategy to get comprehensive, maintainable coverage of the characteristics of the system.

第 10 章部署和发布应用程序

Chapter 10. Deploying and Releasing Applications

介绍

Introduction

将软件发布到生产环境和将其部署到测试环境之间存在差异——尤其是在执行发布的人员血液中的肾上腺素水平方面。但是,从技术上讲,这些差异应该封装在一组配置文件中。当部署到生产环境时,应遵循与任何其他部署相同的过程。启动你的自动部署系统,给它你要部署的软件版本和目标环境的名称,然后点击开始。同样的过程也应该用于所有后续部署和发布。

There are differences between releasing software into production and deploying it to testing environments—not least, in the level of adrenaline in the blood of the person performing the release. However, in technical terms, these differences should be encapsulated in a set of configuration files. When deployment to production occurs, the same process should be followed as for any other deployment. Fire up your automated deployment system, give it the version of your software to deploy and the name of the target environment, and hit go. This same process should also be used for all subsequent deployments and releases.

由于两者使用相同的过程,因此本章涉及部署和发布软件。我们将描述如何创建和遵循发布软件的策略,包括部署到测试环境。部署和发布之间的主要区别在于回滚的能力,我们在本章中详细讨论了这个问题。我们还介绍了两种非常强大的技术,可用于在最大的生产系统上执行零停机时间发布和回滚:蓝绿部署和金丝雀发布。

Since the same process is used for both, this chapter deals with both deploying and releasing software. We will describe how to create and follow a strategy for releasing software, including deployments to testing environments. The main difference between deploying and releasing is the ability to roll back, and we deal with this problem at length in this chapter. We also introduce two extremely powerful techniques that can be used to perform zero-downtime releases and rollbacks on even the largest of production systems: blue-green deployments and canary releasing.

所有这些流程(部署到测试和生产环境以及回滚)都需要构成部署管道实施的一部分。应该可以看到可用于部署到每个环境中的构建列表,并通过按下按钮或单击鼠标来选择要部署的版本和要将其部署到的环境来运行自动部署过程。事实上,这应该是对这些环境进行任何类型更改的唯一方法——包括操作系统和第三方软件的配置。因此,可以准确查看您的应用程序的哪些版本在哪些环境中、谁授权部署以及自上次部署以来对应用程序进行了哪些更改。

All of these processes—deploying to testing and production environments and rolling back—need to form part of your deployment pipeline implementation. It should be possible to see a list of builds available for deployment into each of these environments and run the automated deployment process by pressing a button or clicking a mouse to select the version to deploy and the environment to deploy it to. This, in fact, should be the only way to make changes to these environments of any kind—including the configuration of the operating system and third-party software. Thus it becomes possible to see exactly which versions of your application are in which environments, who authorized the deployment, and what changes have been made to the application since the last time it was deployed.

我们将在本章集中讨论将应用软件部署到多个用户共享的环境中的问题,尽管相同的原则将适用于用户安装的软件。特别是,我们讨论了发布产品和确保持续交付客户端安装的软件。

We will be concentrating in this chapter on the problem of deploying application software to environments shared by multiple users, although the same principles will apply to user-installed software. In particular, we discuss releasing products and ensuring continuous delivery of client-installed software.

创建发布策略

Creating a Release Strategy

创建发布策略最重要的部分是让应用程序的涉众在项目规划过程中会面。他们讨论的重点应该是就应用程序在其整个生命周期中的部署和维护达成共识。然后将这种共享理解捕获为发布策略。在应用程序的整个生命周期中,利益相关者将更新和维护该文档。

The most important part of creating a release strategy is for the application’s stakeholders to meet up during the project planning process. The point of their discussions should be working out a common understanding concerning the deployment and maintenance of the application throughout its lifecycle. This shared understanding is then captured as the release strategy. This document will be updated and maintained by the stakeholders throughout the application’s life.

在项目开始时创建发布策略的第一个版本时,您应该考虑包括以下内容:

When creating the first version of your release strategy at the beginning of the project, you should consider including the following:

• 负责部署到每个环境以及负责发布的各方。

• Parties in charge of deployments to each environment, as well as in charge of the release.

• 资产和配置管理策略。

• An asset and configuration management strategy.

• 用于部署的技术描述。这应该得到运营和开发团队的同意。

• A description of the technology used for deployment. This should be agreed upon by both the operations and development teams.

• 实施部署管道的计划。

• A plan for implementing the deployment pipeline.

• 可用于验收、容量、集成和用户验收测试的环境的枚举,以及构建将在这些环境中移动的过程。

• An enumeration of the environments available for acceptance, capacity, integration, and user acceptance testing, and the process by which builds will be moved through these environments.

• 对部署到测试和生产环境中要遵循的流程的描述,例如要打开的变更请求和需要授予的批准。

• A description of the processes to be followed for deployment into testing and production environments, such as change requests to be opened and approvals that need to be granted.

• 监控应用程序的要求,包括应用程序应该用来通知运营团队其状态的任何API 或服务。

• Requirements for monitoring the application, including any APIs or services the application should use to notify the operations team of its state.

• 讨论管理应用程序部署时和运行时配置的方法,以及这与自动部署过程的关系。

• A discussion of the method by which the application’s deploy-time and runtime configuration will be managed, and how this relates to the automated deployment process.

• 与任何外部系统集成的描述。在什么阶段以及如何将它们作为发布的一部分进行测试?出现问题时运维人员如何与供应商沟通?

• Description of the integration with any external systems. At what stage and how are they tested as part of a release? How do the operations personnel communicate with the provider in the event of a problem?

• 日志详细信息,以便操作人员可以确定应用程序的状态并识别任何错误情况。

• Details of logging so that operations personnel can determine the application’s state and identify any error conditions.

• 灾难恢复计划,以便在灾难发生后可以恢复应用程序的状态。

• A disaster recovery plan so that the application’s state can be recovered following a disaster.

• 软件的服务级别协议,它将决定应用程序是否需要故障转移和其他高可用性策略等技术。

• The service-level agreements for the software, which will determine whether the application will require techniques like failover and other high-availability strategies.

• 生产规模和容量规划:您的实时应用程序将创建多少数据?您需要多少日志文件或数据库?您需要多少带宽和磁盘空间?客户期望的延迟是多少?

• Production sizing and capacity planning: How much data will your live application create? How many log files or databases will you need? How much bandwidth and disk space will you need? What latency are clients expecting?

• 归档策略,以便可以保留不再需要的生产数据用于审计或支持目的。

• An archiving strategy so that production data that is no longer needed can be kept for auditing or support purposes.

• 初始部署到生产的工作方式。

• How the initial deployment to production works.

• 如何修复缺陷并将补丁应用于生产环境。

• How fixing defects and applying patches to the production environment will be handled.

• 如何处理生产环境的升级,包括数据迁移。

• How upgrades to the production environment will be handled, including data migration.

• 如何管理应用程序支持。

• How application support will be managed.

创建发布策略的行为是有用的:它通常是软件开发和硬件环境的设计、配置和调试的功能性和非功能性需求的来源。这些要求应该被识别出来,并在发现时添加到开发计划中。

The act of creating a release strategy is useful: It will usually be a source of both functional and nonfunctional requirements for both software development and for the design, configuration, and commissioning of hardware environments. These requirements should be recognized as such and added to the development plan as they are discovered.

制定战略当然只是一个开始;随着项目的进展,它将被添加和更改。

Creating the strategy is of course just the beginning; it will be added to and changed as the project progresses.

发布策略的一个重要组成部分是描述如何执行发布的发布计划。

A vital component of the release strategy is the release plan describing how releases are performed.

发布计划

The Release Plan

第一个版本通常是风险最高的版本;它需要仔细规划。此计划的结果可能是自动化脚本、文档或其他将应用程序可靠且重复地部署到生产环境中所需的过程。除了发布策略中的材料外,还应该包括

The first release is usually the one that carries the highest risk; it needs careful planning. The results of this planning may be automated scripts, documentation, or other procedures needed to reliably and repeatedly deploy the application into the production environment. In addition to the material in the release strategy, it should include

• 首次部署应用程序所需的步骤

• The steps required to deploy the application for the first time

• 如何对应用程序及其在部署过程中使用的任何服务进行冒烟测试

• How to smoke-test the application and any services it uses as part of the deployment process

• 部署出错时退出部署所需的步骤

• The steps required to back out the deployment should it go wrong

• 备份和恢复应用程序状态所需的步骤

• The steps required to back up and restore the application’s state

• 在不破坏应用程序状态的情况下升级应用程序所需的步骤

• The steps required to upgrade the application without destroying the application’s state

• 失败时重新启动或重新部署应用程序的步骤

• The steps to restart or redeploy the application should it fail

• 日志的位置和它们包含的信息的描述

• The location of the logs and a description of the information they contain

• 监控应用程序的方法

• The methods of monitoring the application

• 作为发布的一部分,执行任何必要的数据迁移的步骤

• The steps to perform any data migrations that are necessary as part of the release

• 以前部署问题及其解决方案的问题日志

• An issue log of problems from previous deployments, and their solutions

有时还有其他考虑因素需要补充。例如,如果您的新软件是从遗留系统接管的,您应该记录将用户转移到新系统和停用旧系统的步骤,如果出现问题不要忘记回滚过程。

There are sometimes other considerations to add. For example, if your new software is taking over from a legacy system, you should document the steps to transfer users to the new system and decommission the old system, not forgetting a rollback process if things go wrong.

同样,随着项目的进展和获得新的见解,需要维护该计划。

Again, this plan will need to be maintained as the project progresses and new insights are gained.

发布产品

Releasing Products

上面列出的策略和计划是相当通用的。它们对于所有项目都值得考虑,即使经过一番考虑后您决定只使用其中的几个部分。

The strategies and plans listed above are fairly generic. They are worth considering for all projects, even if, after some consideration, you decide to only use a few of the sections.

您必须考虑其他问题的一类软件项目是注定要作为商业产品发布的软件。如果您的项目的输出是软件产品,则应考虑以下额外可交付成果的列表:

One class of software projects where you must consider other issues is software destined to be released as a commercial product. Here’s a list of additional deliverables that should be considered if the output of your project is a software product:

• 定价模式

• Pricing model

• 许可策略

• Licensing strategy

• 关于所用第三方技术的版权问题

• Copyright issues around third-party technologies used

• 打包

• Packaging

• 营销材料——印刷品、基于网络的、播客、博客、新闻稿、会议等。

• Marketing materials—print, web-based, podcasts, blogs, press releases, conferences, etc.

• 产品文档

• Product documentation

• 安装人员

• Installers

• 准备销售和支持团队

• Preparing sales and support teams

部署和推广您的应用程序

Deploying and Promoting Your Application

以可靠、一致的方式部署任何应用程序的关键是不断实践:使用相同的流程部署到每个环境,包括生产环境。自动化部署应该从第一次部署到测试环境开始。与其手动将软件的各个部分成形,不如编写一个简单的脚本来完成这项工作。

The key to deploying any application in a reliable, consistent manner is constant practice: Use the same process to deploy to every environment, including production. Automating the deployment should start with the very first deployment to a testing environment. Instead of manually pulling the pieces of software into shape, write a simple script that does the job.

第一次部署

The First Deployment

当您向客户展示您的第一个故事或需求时,任何应用程序的首次部署都应该发生在第一次迭代中。选择一个或两个具有高优先级但非常容易在您的第一次迭代中交付的故事或需求(假设您的迭代是一到两周并且您有一个小团队——如果这些条件不适用,您应该选择更多)。使用此展示作为使应用程序可部署到类似生产的展示环境 (UAT) 的原因。在我们看来,项目第一次迭代的主要目标之一是让部署管道的早期阶段运行起来,并能够部署和演示一些东西,无论多么小,最后。这是我们建议将技术价值优先于业务价值的极少数情况之一。您可以将此策略视为启动开发过程的泵。

The first deployment of any application should happen in the first iteration when you showcase your first stories or requirements to the customer. Choose one or two stories or requirements that are of high priority but very simple to deliver in your first iteration (assuming your iterations are one or two weeks and you have a small team—you should choose more if these conditions do not apply). Use this showcase as a reason to make the application deployable to a production-like showcase environment (UAT). In our minds, one of the principal goals of the first iteration of a project is to get the early stages of our deployment pipeline running and to be able to deploy and demonstrate something, no matter how small, at the end. This is one of the very few situations where we recommend prioritizing technical value over business value. You can think of this strategy as priming the pump of your development process.

在此泵启动迭代结束时,您应该准备好以下内容:

At the end of this pump-priming iteration, you should have the following in place:

• 您的部署管道的提交阶段

• Your deployment pipeline’s commit stage

• 要部署到的类生产环境

• A production-like environment to deploy to

• 一个自动化过程,它获取提交阶段创建的二进制文件并将它们部署到环境中

• An automated process that takes the binaries created by your commit stage and deploys them into the environment

• 一个简单的冒烟测试,用于验证部署是否有效以及应用程序是否正在运行

• A simple smoke test that verifies that the deployment worked and the application is running

对于一个才刚刚活跃开发了几天的应用程序来说,这应该不是什么大问题。这里棘手的一点是弄清楚环境应该如何像生产一样。您的部署目标不需要是最终生产环境的克隆,但生产环境的某些方面比其他方面更重要。

This shouldn’t be too much trouble for an application that has only been under active development for a few days. The tricky bit here is working out how production-like the environment should be. Your deployment target does not need to be a clone of the eventual production environment, but there are some aspects of the production environment that are more important than others.

一个很好的问题是,“生产环境与我的开发环境有何不同?” 如果生产环境在不同的操作系统上运行,您应该使用将在生产环境中用于 UAT 环境的相同操作系统。如果您的生产环境是一个集群,您应该为您的暂存环境构建一个小型的、有限的集群。如果您的生产环境是具有许多不同节点的分布式环境,确保您的类似生产的测试环境至少有一个单独的流程来代表每一类流程边界。

A good question to ask is, “How different is the production environment from my development environment?” If the production environment runs on a different operating system, you should use the same operating system that will be used in production for your UAT environment. If your production environment is a cluster, you should build a small, limited cluster for your staging environment. If your production environment is a distributed one with many different nodes, make sure your production-like test environment has at least one separate process to represent each class of process boundary.

虚拟化和数鸡(0、1、很多)是您的朋友。虚拟化使得创建代表生产环境重要方面的环境变得容易,同时能够在单个物理机器上运行。小鸡计数意味着如果您的生产站点有 250 台 Web 服务器,那么 2 台应该足以代表重要的流程边界。稍后,随着开发的进展,您可以变得更加复杂。

Virtualization and chicken-counting (0, 1, many) are your friends here. Virtualization makes it easy to create an environment that represents the important aspects of your production environment, while being able to run on a single physical machine. Chicken-counting means that if your production site has 250 web servers, 2 should be enough to represent the significant process boundaries. Later on, as development progresses, you can get more sophisticated.

通常,类生产环境具有以下特征。

In general, a production-like environment has the following characteristics.

• 它应该运行与生产系统相同的操作系统。

• It should run the same operating system as the production system will.

• 它应该安装与生产系统相同的软件——特别是,不应在其上安装任何开发工具链(例如编译器或IDE)。

• It should have the same software installed as the production system will—and in particular, none of the development toolchain (such as compilers or IDEs) should be installed on it.

• 在合理的范围内,应使用第 11 章“管理基础设施和环境”中描述的技术,以与生产环境相同的方式管理此环境。

• This environment should, as far as is reasonable, be managed the same way as the production environment, using the techniques described in Chapter 11, “Managing Infrastructure and Environments.”

• 在客户端安装软件的情况下,您的UAT 环境应该代表您客户端的硬件统计数据,或者至少是其他人的真实世界统计数据。1个

• In the case of client-installed software, your UAT environment should be representative of your clients’ hardware statistics, or at least someone else’s real-world statistics.1

对您的发布过程建模并促进构建

Modeling Your Release Process and Promoting Builds

随着您的应用程序增长并变得更加复杂,您的部署管道实施也将如此。由于您的部署管道应该模拟您的测试和发布过程,因此您首先需要弄清楚这个过程是什么。虽然这通常表示为促进环境之间的构建,但我们关心更多细节。特别重要的是要捕捉

As your application grows and becomes more complex, so will your deployment pipeline implementation. Since your deployment pipeline should model your test and release process, you need first to work out what this process is. While this is often expressed in terms of promoting builds between environments, there are more details that we care about. In particular, it is important to capture

• 构建必须经过哪些阶段才能发布(例如,集成测试、QA 验收测试、用户验收测试、暂存、生产)

• What stages a build has to go through in order to be released (for example, integration testing, QA acceptance testing, user acceptance testing, staging, production)

• 需要什么关卡或批准

• What the required gates or approval are

• 对于每个门,谁有权批准通过该门的构建

• For each gate, who has the authority to approve a build passing through that gate

图 10.1 示例测试和发布过程图

Figure 10.1 An example test and release process diagram

图片

在本练习结束时,您可能会得到类似于图 10.1的图表。当然,您的过程可能比这更复杂或更简单。实际上,创建这样的图表是创建价值流的第一步发布过程的地图。我们在第 5 章“部署管道剖析”中讨论了价值流映射作为优化发布流程的一种方式。

At the end of this exercise, you might end up with a diagram similar to Figure 10.1. Of course, your process may be more or less complex than this. Creating a diagram like this is, in fact, the first step to creating a value stream map for your release process. We discussed value stream mapping as a way to optimize your release process in Chapter 5, “Anatomy of the Deployment Pipeline.”

创建此图后,您可以在用于管理部署的工具中为发布过程的每个部分创建占位符。Go 和 AntHill Pro 都允许您开箱即用地执行此操作,并且大多数持续集成工具都可以通过一些工作来建模和管理此过程。完成此操作后,负责批准的人员应该可以使用您的工具批准通过发布过程中的关卡的特定构建。

Once you’ve created this diagram, you can create placeholders for each part of your release process in the tool you use for managing deployments. Go and AntHill Pro both allow you to do this out of the box, and most continuous integration tools can model and manage this process with some work. Once this is done, it should be possible for the people responsible for approvals to approve, using your tool, a particular build moving through a gate in the release process.

用于管理部署管道的工具必须提供的另一个基本功能是,对于每个阶段,能够查看哪些构建已通过管道中的所有先前阶段并因此为下一阶段做好准备。然后应该可以选择这些构建之一并按下按钮来部署它。此过程称为提升。按下按钮即可促进构建,将部署管道转变为拉式系统,让参与交付过程的每个人都能够管理自己的工作。分析师和测试人员可以自助部署以进行探索性测试、展示或可用性测试。操作人员只需按一下按钮,就可以将他们选择的任何版本部署到暂存或生产环境中。

The other essential facility that must be provided by the tool you use to manage your deployment pipeline is the ability, for each stage, to see which builds have passed all the previous stages in the pipeline and are hence ready for the next stage. It should then be possible to choose one of these builds and press a button to have it deployed. This process is known as promotion. Promoting builds at the press of a button is what turns the deployment pipeline into a pull system, giving everybody involved in the delivery process the ability to manage their own work. Analysts and testers can self-service deployments for exploratory testing, showcasing, or usability testing. Operations personnel can deploy any version of their choice to staging or production at the press of a button.

自动部署机制使升级成为一件简单的事情,只需选择所需的候选发布版本并等待它部署到正确的环境即可。这种自动部署机制应该可供需要部署应用程序的任何人使用。它不需要对部署本身的技术细节有任何了解或理解。为此,一旦部署系统认为系统准备就绪,就包含自动冒烟测试是非常有帮助的。这样,我们可以向请求部署的人员(无论是分析师、测试人员还是操作人员)保证系统已准备好使用并按应有的方式工作,或者如果没有,则可以轻松诊断原因.

An automated deployment mechanism makes promotion a simple matter of selecting the desired release candidate and waiting for it to deploy to the correct environment. This automated deployment mechanism should be usable by anyone who needs to deploy the application. It should not require any knowledge or understanding of the technicalities of the deployment itself. To this end, it is very helpful to include automated smoke tests that run once the deployment system thinks that the system is ready. This way, we can assure the person requesting a deployment—whether it is an analyst, tester, or operations person—that the system is ready for use and working as it should be, or make it easy to diagnose the cause if it is not.

测试和发布过程中的每个阶段都涉及基本相同的工作流程:测试应用程序的特定版本以确定其是否适合根据一组验收标准发布。为了执行此测试,需要将所选的应用程序构建部署到环境中。如果您的应用程序是需要手动测试的用户安装软件,则此环境可能是测试人员的台式机。对于嵌入式软件,这可能需要专门的专用硬件环境。在托管软件服务的情况下,它可能是一组类似于生产的盒子。或者它可能是这些的组合。

Each stage in your test and release process involves basically the same workflow: testing a particular version of the application to determine its fitness to be released according to a set of acceptance criteria. In order to perform this testing, the chosen build of the application needs to be deployed into an environment. This environment might be the tester’s desktop machine, if your application is user-installed software that needs to be manually tested. In the case of embedded software, this might require a specialized, dedicated hardware environment. In the case of a hosted software service, it might be a set of boxes that resemble production. Or it might be a combination of these.

在任何这些情况下,流程中任何测试阶段的工作流程都是相似的。

In any of these cases, the workflow for any of the testing stages in the process will be similar.

1. 进行测试的人员或团队应该有办法选择他们想要将哪个版本的应用程序部署到他们的测试环境中。此列表将包括已通过部署管道的所有先前阶段的应用程序的所有版本。选择一个特定的构建应该会导致以下步骤自动执行,直至实际测试。

1. The person or team doing the testing should have a way to select which version of the application they want to deploy into their testing environment. This list will include all versions of the application that have passed all the prior stages of the deployment pipeline. Selecting a particular build should cause the following steps, up to the actual testing, to be performed automatically.

2. 准备环境和相关基础设施(包括中间件),使其处于干净状态,为应用程序的部署做好准备。这应该以完全自动化的方式完成,如第 11 章“管理基础设施和环境”中所述。

2. Prepare the environment and associated infrastructure (including middleware) so it is in a clean state, ready for deployment of the application. This should be done in a fully automated fashion, as described in Chapter 11, “Managing Infrastructure and Environments.”

3. 部署应用程序的二进制文件。这些二进制文件应始终从工件存储库中获取,而不是为每个部署从头开始构建。

3. Deploy the application’s binaries. These binaries should always be fetched from the artifact repository, never built from scratch for each deployment.

4. 配置应用程序。配置信息应该在所有应用程序中以一致的方式管理,并在部署时或运行时应用,使用像 Escape [apvrEr] 这样的工具。第 2 章“配置管理”中有关于此主题的更多信息。

4. Configure the application. Configuration information should be managed in a consistent way across all applications and applied at deployment time or run time, using a tool like Escape [apvrEr]. There’s more information on this subject in Chapter 2, “Configuration Management.”

5. 准备或迁移应用程序管理的任何数据,如第 12 章“管理数据”中所述。

5. Prepare or migrate any data managed by the application, as described in Chapter 12, “Managing Data.”

6. 对部署进行冒烟测试。

6. Smoke-test the deployment.

7. 执行测试(这可能是手动的或自动的)。

7. Perform the testing (this might be manual or automated).

8. 如果该版本的应用程序通过测试,则批准其升级到下一个环境。

8. If this version of the application passes the tests, approve its promotion to the next environment.

9. 如果这个版本的应用程序没有通过测试,记录原因。

9. If this version of the application does not pass the tests, record why.

提升配置

Promoting Configuration

需要提升的不仅仅是二进制文件。环境的配置和应用的配置也需要同时推进。为了使事情变得更复杂,您不想提升所有配置。例如,您需要确保提升任何新的配置设置,但您不想将指向您的应用程序指向 SIT 数据库或外部服务的测试替身的设置提升到生产环境。管理与应用程序相关联的某些配置位的提升——而不是与环境相关联的其他配置位——是一个复杂的问题。

It is not just the binaries that need to be promoted. The configuration of the environment and of the application also need to be promoted at the same time. To make things more complex, you don’t want to promote all of the configuration. For example, you need to make sure that any new configuration settings get promoted, but you won’t want to promote to production a setting that points your application at your SIT database or a test double of an external service. Managing the promotion of certain bits of configuration associated with an application—but not others which are associated instead with an environment—is a complex problem.

解决这个问题的一种方法是让你的冒烟测试验证你指向的是正确的东西。例如,您可以让测试替身服务以字符串的形式返回它期望与之对话的环境,并让冒烟测试检查您的应用程序从外部服务返回的字符串是否与它正在部署的环境相匹配。对于中间件配置,例如线程池,您可以使用 Nagios 等工具监控这些设置。您还可以编写基础结构测试来检查任何关键设置并将它们报告给您的监控软件。第 323 页的“行为驱动监控”部分提供了更多详细信息。

One way to attack this problem is to make your smoke tests verify that you are pointing at the right things. For example, you could have a test double service return the environment it expects to talk to as a string, and have the smoke tests check that the string your application gets back from an external service matches the environment it is deploying to. In the case of middleware configuration, such as thread pools, you can monitor these settings using a tool like Nagios. You can also write infrastructure tests that check any key settings and report them to your monitoring software. The “Behavior-Driven Monitoring” section on page 323 provides more detail.

在面向服务的架构和组件化应用的情况下,构成应用的所有服务或组件需要一起提升。正如我们在上一节中所讨论的,通常在系统集成测试环境中可以找到应用程序服务和组件版本的良好组合。您的部署系统需要强制将该组合作为一个整体进行提升,以避免有人部署了其中一项服务的错误版本,导致应用程序失败,或者更糟的是,引入间歇性和难以跟踪的服务-羽绒缺陷。

In the case of service-oriented architectures and componentized applications, all the services or components forming the application need to be promoted together. As we discussed in the previous section, it is usually in the system integration testing environment that a good combination of versions of the application’s services and components is found. Your deployment system needs to enforce that this combination is then promoted as a whole, to avoid a situation where someone deploys a wrong version of one of the services, causing the application to fail—or worse, introducing an intermittent and hard-to-track-down defect.

编排

Orchestration

环境通常在多个应用程序之间共享。这可能会以两种方式引起并发症。首先,这意味着您在为应用程序的新部署准备环境时必须格外小心,以免干扰该环境中任何其他应用程序的运行。这通常意味着确保对操作系统或任何中间件配置的更改不会导致其他应用程序行为异常。如果生产环境在相同的应用程序之间共享,那么这项工作有一个有用的目的:确保所选的应用程序版本之间没有冲突。如果事实证明这是一项复杂的工作,您可以考虑使用某种形式的虚拟化技术将应用程序彼此隔离。

Environments are often shared between several applications. This can cause complications in two ways. First, it means you have to take extra care when preparing the environment for a new deployment of an application so as to not disturb the operation of any other applications in this environment. This normally means ensuring that changes to the configuration of the operating system or any middleware don’t cause the other applications to misbehave. If the production environment is shared between the same applications, then this work serves a useful purpose: ensuring that there are no conflicts between the chosen versions of the applications. If this turns out to be a complex exercise, you might consider using some form of virtualization technology to isolate the applications from each other.

其次,共享环境的应用程序可能相互依赖。这在使用面向服务的架构时很常见。在这种情况下,集成测试(也称为系统集成测试或 SIT)环境是应用程序第一次相互交谈而不是与某种测试替身交谈。因此,SIT 环境中的大部分工作涉及部署每个应用程序的新版本,直到它们全部协作。在这种情况下,冒烟测试套件通常是针对整个应用程序运行的一组成熟的验收测试。

Second, the applications sharing the environment may depend on each other. This is common when using a service-oriented architecture. In this situation, the integration testing (also called systems integration testing, or SIT) environment is the first time that the applications will be talking to each other rather than to a test double of some kind. Thus, much of the work in the SIT environment involves deploying new versions of each of the applications until they all cooperate. In this situation the smoke test suite is usually a fully fledged set of acceptance tests that run against the whole application.

部署到暂存环境

Deployments to Staging Environments

在让毫无戒心的用户使用您的应用程序之前,您应该在与生产环境非常相似的暂存环境中执行一些最终测试。如果您设法获得了一个接近生产副本的容量测试环境,那么有时跳过暂存步骤可能是有意义的:您可以将容量测试环境用于容量测试和暂存。不过,一般来说,我们不建议对简单系统以外的任何事物使用此方法。如果您的应用程序包括与外部系统的任何集成,那么您可以最终确认集成在每个系统的预期生产版本之间工作的所有方面。

Before you let your application loose on unsuspecting users, you should perform some final tests in a staging environment that is very similar to production. If you managed to get a capacity testing environment that is a close replica of production, it may sometimes make sense to skip the staging step: You can employ the capacity testing environment for both capacity testing and staging. In general, though, we would recommend against this for anything other than simple systems. If your application includes any integration with external systems, staging is the point where you get a final confirmation that all aspects of integration work between the intended production versions of each system.

您应该在项目开始时就开始将暂存环境放在一起。如果您有用于生产的硬件并且它没有被用于任何其他用途,请将其用作暂存环境,直到您执行第一个版本。以下是从项目开始就需要计划的一些事情:

You should have started to put your staging environment together at the beginning of your project. If you have the hardware for production and it is not being used for anything else, use it as a staging environment until you perform your first release. Here are some things to plan from the beginning of the project:

• 确保您的生产、容量测试和暂存环境得到调试。特别是,在未开发项目中,在发布前一段时间准备好生产环境,并将其作为管道的一部分部署到其中。

• Ensure your production, capacity testing, and staging environments are commissioned. In particular, on a green field project, have your production environment ready some time before the release, and deploy to it as part of your pipeline.

• 有一个自动化的过程来配置您的环境,包括网络、外部服务和基础设施。

• Have an automated process for configuring your environment, including networks, external services, and infrastructure.

• 确保部署过程经过充分的冒烟测试。

• Ensure the deployment process is adequately smoke-tested.

• 测量应用程序的预热期。如果您的应用程序使用缓存,这尤其适用。将此纳入您的部署计划。

• Measure the warm-up period for your application. This is especially applicable if your application uses caching. Incorporate this into your deployment plan.

• 测试与外部系统的集成。您不希望您的应用程序的发布是您第一次针对真实的外部系统运行。

• Test integration with external systems. You don’t want your application’s release to be the first time you run against the real external systems.

• 如果可能,请在发布之前尽早将您的应用程序置于其生产环境中。如果“发布”可以像重新配置一些路由器一样简单,将流量从保留页面引导到您的生产环境,那就更好了。这种称为蓝绿部署的技术将在本章稍后部分进行介绍。

• If possible, get your application into its production environment well before release. If “release” can be as simple as reconfiguring some router to direct traffic from a holding page to your production environment, so much the better. This technique, known as blue-green deployment, is described a little later in this chapter.

• 如果可能,在将系统推广给所有人之前,先尝试将系统推广给一小部分用户。这种技术称为金丝雀发布,本章后面也会介绍。

• If possible, try rolling your system out to a small group of users before you roll it out to everybody. This technique is known as canary releasing, and is also described later in this chapter.

• 将通过验收测试的每个更改部署到您的暂存环境(尽管不一定到生产环境)。

• Deploy every change that passes acceptance tests to your staging environment (although not necessarily to production).

回滚部署和零停机发布

Rolling Back Deployments and Zero-Downtime Releases

重要的是能够回滚部署以防出错。在运行的生产环境中调试问题几乎肯定会导致深夜、错误导致不幸的后果以及愤怒的用户。您需要有一种方法可以在出现问题时为您的用户恢复服务,这样您就可以在正常工作时间舒适地调试故障。我们将在此处讨论执行回滚的几种方法。更高级的技术——蓝绿部署和金丝雀发布——也可用于执行零停机发布和回滚。

It is essential to be able to roll back a deployment in case it goes wrong. Debugging problems in a running production environment is almost certain to result in late nights, mistakes with unfortunate consequences, and angry users. You need to have a way to restore service to your users when things go wrong, so you can debug the failure in the comfort of normal working hours. There are several methods of performing a rollback that we will discuss here. The more advanced techniques—blue-green deployments and canary releasing—can also be used to perform zero-downtime releases and rollbacks.

在我们开始之前,有两个重要的约束。首先是您的数据。如果您的发布过程对您的数据进行了更改,则可能很难回滚。另一个限制是您集成的其他系统。对于涉及多个系统的发布(称为编排发布),回滚过程也变得更加复杂。

Before we start, there are two important constraints. The first is your data. If your release process makes changes to your data, it can be hard to roll back. Another constraint is the other systems you integrate with. With releases involving more than one system (known as orchestrated releases), the rollback process becomes more complex too.

在制定回滚发布计划时,您应该遵循两个一般原则。首先是确保在发布之前备份生产系统的状态,包括数据库和文件系统中保存的状态。第二个是练习你的回滚计划,包括从备份中恢复或在每次发布之前将数据库迁移回来以确保它能正常工作。

There are two general principles you should follow when creating a plan for rolling back a release. The first is to ensure that the state of your production system, including databases and state held on the filesystem, is backed up before doing a release. The second is to practice your rollback plan, including restoring from the backup or migrating the database back before every release to make sure it works.

通过重新部署以前的好版本回滚

Rolling Back by Redeploying the Previous Good Version

这通常是最简单的回滚方式。如果您有部署应用程序的自动化流程,恢复到良好状态的最简单方法是从头开始重新部署以前的良好版本。这还将包括重新配置它运行的环境,因此它的配置方式与以前完全相同。这就是能够从头开始重新创建环境如此重要的原因之一。

This is often the simplest way to roll back. If you have an automated process for deploying your application, the simplest way to get back to a good state is to redeploy the previous good version from scratch. This will also include reconfiguring the environment it runs on, so it becomes configured precisely the same way that it was before. This is one of the reasons it is so important to be able to re-create environments from scratch.

为什么要创建环境并从头开始部署?有几个很好的理由:

Why create the environment and do the deployment from scratch? There are a few good reasons:

• 如果您没有自动回滚流程但有自动部署流程,那么重新部署最后一个版本是一个固定时间的操作,风险较低(因为出错的可能性较小)。

• If you do not have an automated rollback process but you do have an automated deployment process, then redeploying the last version is a fixedtime operation that poses a lower risk (because there is less to go wrong).

• 这是您之前(希望如此)测试过数百次的相同过程。回滚的执行频率要低得多,因此更有可能包含错误。

• It is the same process you have tested (hopefully) hundreds of times before. Rollbacks are performed much less frequently, and therefore are more likely to contain bugs.

我们想不出任何这不起作用的情况。但是,也有一些缺点:

We can’t think of any situations where this will not work. However, there are some disadvantages:

• 尽管重新部署旧版本所需的时间是固定的,但它不是零。因此会导致停机。

• Even though the time it takes to redeploy the old version is fixed, it is nonzero. It will thus lead to a downtime.

• 更难调试出错的地方。重新部署旧版本通常会覆盖新版本,从而消除了弄清楚发生了什么的机会。如果您的生产环境是虚拟的,这可以得到缓解,我们稍后将对此进行描述。对于相对简单的应用程序,通常很容易通过将每个版本部署到新目录并使用符号链接指向当前版本来保留旧版本。

• It makes it harder to debug what went wrong. Redeploying the old version often overwrites the new version, thereby removing the opportunity to work out what happened. This can be mitigated if your production environment is virtual, which we describe later on. With relatively simple applications, it’s often easy to keep the old version around by deploying each version to a new directory and using symbolic links to point to the current version.

• 如果您从部署最新版本之前的数据库备份中恢复,您将丢失部署后创建的所有数据。如果您相当快地回滚,这可能不是什么大问题,但在某些情况下,这是不可接受的。

• If you restore from the database backup you took before deploying the latest version, you will lose any data created following the deployment. This may not be a big deal if you roll back reasonably quickly, but in some situations this is not acceptable.

零停机发布

Zero-Downtime Releases

零停机版本,也称为热部署,是指将用户从一个版本切换到另一个版本的实际过程几乎是瞬间发生的。至关重要的是,如果出现问题,还必须能够几乎立即将用户退回到以前的版本。

A zero-downtime release, also known as hot deployment, is one in which the actual process of switching users from one release to another happens nearly instantaneously. Crucially, it must also be possible to back users out to the previous version nearly instantaneously too, if something goes wrong.

零停机发布的关键是将发布过程的各个部分解耦,以便它们可以尽可能独立地发生。特别是,在升级应用程序之前,应该可以安装应用程序所依赖的共享资源的新版本,例如数据库、服务和静态资源。

The key to zero-downtime releases is decoupling the various parts of the release process so they can happen independently as far as possible. In particular, it should be possible to put in place new versions of shared resources your applications depend on, such as databases, services, and static resources, before you upgrade your applications.

使用静态资源和基于 Web 的服务,这就相对容易了。您只需在 URI 中包含资源或服务的版本,就可以同时使用它们的多个版本。例如,Amazon Web Services 有一个基于日期的版本控制系统,最新版本的 EC2 API(在撰写本文时)位于http://ec2.amazonaws.com/doc/2009-11-30/AmazonEC2 .wsdl当然,他们保持早期版本的 API 在旧 URI 上也能正常工作。对于资源,当你推出一个新版本的网站时,你将图片、Javascript、HTML 和 CSS 等静态资源放到一个新的目录中——例如,你可以将应用程序 2.6.5 版本的图片放在新的目录中在/static/2.6.5/images下。

With static resources and web-based services, this is relatively easy. You just include the version of the resource or service in the URI, and you can have multiple versions of them available simultaneously. For example, Amazon Web Services has a date-based versioning system, with the latest version of the EC2 API (at the time of writing) available at http://ec2.amazonaws.com/doc/2009-11-30/AmazonEC2.wsdl. Of course, they keep the earlier versions of the API working as well at the old URIs. For resources, when you push a new version of your website out, you put the static resources such as images, Javascript, HTML, and CSS to a new directory—for example, you could put the images for version 2.6.5 of your application under /static/2.6.5/images.

数据库有点困难。第 12 章“管理数据”中有一节专门介绍在零停机情况下管理数据库。

Things are a little harder with databases. There is a section dedicated to managing databases in a zero-downtime scenario in Chapter 12, “Managing Data.”

蓝绿部署

Blue-Green Deployments

这是我们所知道的用于管理发布的最强大的技术之一。我们的想法是拥有两个相同版本的生产环境,我们称之为蓝色和绿色。

This is one of the most powerful techniques we know for managing releases. The idea is to have two identical versions of your production environment, which we’ll call blue and green.

图 10.2 蓝绿部署

Figure 10.2 Blue-green deployments

图片

图 10.2的示例中,系统的用户被路由到绿色环境,即当前指定的生产环境。我们想发布一个新版本的应用程序。所以我们把它部署到蓝色环境,让应用预热(这个你想做多少就做多少)。这丝毫不影响绿化环境的运作。我们可以对蓝色环境运行冒烟测试来检查它是否正常工作。准备就绪后,迁移到新版本就像更改路由器配置以指向蓝色环境而不是绿色环境一样简单。蓝色环境因此成为生产环境。这种切换通常可以在不到一秒的时间内完成。

In the example in Figure 10.2, users of the system are routed to the green environment, which is the currently designated production. We want to release a new version of the application. So we deploy it to the blue environment, and let the application warm up (you can do this as much as you like). This does not in any way affect the operation of the green environment. We can run smoke tests against the blue environment to check it is working properly. When we’re ready, moving to the new version is as simple as changing the router configuration to point to the blue environment instead of the green environment. The blue environment thus becomes production. This switchover can typically be performed in much less than a second.

如果出现问题,我们只需将路由器切换回绿色环境即可。然后我们可以在蓝色环境中调试出了什么问题。

If something goes wrong, we simply switch the router back to the green environment. We can then debug what went wrong on the blue environment.

可以看出,这种方法比重新部署方法产生了一些改进。但是,在使用蓝绿部署管理数据库时需要小心。通常不可能直接从绿色数据库切换到蓝色数据库,因为如果模式发生变化,将数据从一个版本迁移到下一个版本需要时间。

It can be seen that this approach yields several improvements over the redeployment approach. However, some care is needed when managing databases with blue-green deployments. It is usually not possible to switch over directly from the green database to the blue database because it takes time to migrate the data from one release to the next if there are schema changes.

解决此问题的一种方法是在切换前不久将应用程序置于只读模式。然后您可以获取绿色数据库的副本,将其恢复到蓝色数据库中,执行迁移,然后切换到蓝色系统。如果一切正常,您可以将应用程序恢复为读写模式。如果出现问题,您可以简单地切换回绿色数据库。如果在应用程序返回读写模式之前发生这种情况,则无需执行任何操作。如果您的应用程序已将要保留的数据写入新数据库,您将需要找到一种方法来获取新记录并将它们迁移回绿色数据库,然后再尝试发布。或者,您可以找到一种方法,从新版本的应用程序向新旧数据库提供事务处理。

One way to approach this problem is to put the application into read-only mode shortly before switchover. You can then take a copy of the green database, restore it into the blue database, perform the migration, and then switch over to the blue system. If everything checks out, you can put the application back into read-write mode. If something goes wrong, you can simply switch back to the green database. If this happens before the application goes back into read-write mode, nothing more needs to be done. If your application has written data you want to keep to the new database, you will need to find a way to take the new records and migrate them back to the green database before you try the release again. Alternatively, you could find a way to feed transactions to both the new and old databases from the new version of the application.

另一种方法是设计您的应用程序,以便您可以独立于升级过程迁移数据库,我们在第 12 章“管理数据”中对此进行了详细描述。

Another approach is to design your application so that you can migrate the database independently of the upgrade process, which we describe in detail in Chapter 12, “Managing Data.”

如果你只能负担一个单一的生产环境,你仍然可以使用蓝绿部署。只需让您的应用程序的两个副本在同一环境中并排运行。每个副本都有自己的资源——它自己的端口、它自己在文件系统上的根等等——所以它们可以同时运行而不会相互干扰。您可以独立部署到每个环境。另一种方法是使用虚拟化,但您应该首先测试虚拟化对应用程序容量的影响。

If you can only afford a single production environment, you can still use blue-green deployments. Simply have two copies of your application running side by side on the same environment. Each copy has its own resources—its own ports, its own root on the filesystem, and so forth—so they can both be running simultaneously without interfering with each other. You can deploy to each environment independently. Another approach would be to use virtualization, although you should first test the effect of virtualization on the capacity of your application.

如果您有足够的预算,您的蓝色和绿色环境可以是彼此完全独立的副本。这需要较少的配置,但当然更昂贵。这种方法的一种变体,称为shadow domain releasingshadow environment releasinglive-live releasing,是使用你的暂存和生产环境作为你的蓝色和绿色环境。将新版本的应用程序部署到暂存环境,然后将用户从生产环境切换到暂存环境以实时发送新版本的应用程序。此时,staging 变成了 production,production 变成了 staging。

If you have a sufficient budget, your blue and green environments can be completely separate replicas of each other. This requires less configuration, but is of course more expensive. One variant of this approach, known as shadow domain releasing, shadow environment releasing, or live-live releasing, is to use your staging and production environments as your blue and green environments. Deploy the new version of your application to staging, and then switch users from production to the staging environment to send the new version of your application live. At this point, staging becomes production, and production becomes staging.


图片

我们与一家拥有五个并行生产环境的超大型组织合作。他们使用了这种技术,但同时保持多个版本的生产系统并行运行,从而允许他们以不同的速度迁移业务的不同领域。这种方法还具有金丝雀发布的一些特征,如下所述。

We worked with one very large organization that had five parallel production environments. They used this technique, but also kept multiple versions of the production system running in parallel, allowing them to migrate different areas of their business at different rates. This approach also has some characteristics of canary releasing, described below.


金丝雀发布

Canary Releasing

通常可以安全地假设您一次只有一个版本的软件在生产中。这使得管理错误修复变得更加容易,实际上您的基础设施也更容易管理。但是,它也为测试您的软件带来了障碍。即使采用可靠而全面的测试策略,生产中也会出现缺陷。即使周期时间很短,开发团队仍然可以从对新功能的更快反馈以及他们可以做的任何事情中受益,以使他们的软件更有价值。

It is usually a safe assumption that you only have one version of your software in production at a time. This makes it much easier to manage bugfixes, and indeed your infrastructure in general. However, it also presents an impediment to testing your software. Even with a solid and comprehensive testing strategy, defects pop up in production. And even with a low cycle time, development teams could still benefit from faster feedback on the new features and whatever they could be doing to make their software more valuable.

此外,如果您有一个非常大的生产环境,则不可能创建有意义的容量测试环境(除非您的应用程序架构采用端到端共享)。您如何确保您的应用程序的新版本不会表现不佳?

Furthermore, if you have an extremely large production environment, it’s impossible to create a meaningful capacity testing environment (unless your application’s architecture employs end-to-end sharing). How do you ensure a new version of your application won’t perform poorly?

图 10.3 金丝雀发布

Figure 10.3 Canary releasing

图片

金丝雀发布旨在应对这些挑战。金丝雀发布,如图 10.3所示,涉及将应用程序的新版本发布到生产服务器的子集以获得快速反馈。就像煤矿中的金丝雀一样,这可以迅速发现新版本的任何问题,而不会影响大多数用户。这是降低发布新版本风险的好方法。

Canary releasing aims to address these challenges. Canary releasing, as shown in Figure 10.3, involves rolling out a new version of an application to a subset of the production servers to get fast feedback. Like a canary in a coal mine, this quickly uncovers any problems with the new version without impacting the majority of users. This is a great way to reduce the risk of releasing a new version.

与蓝绿部署一样,您需要首先将应用程序的新版本部署到一组没有用户路由到的服务器。然后,您可以在新版本上进行冒烟测试,如果需要,还可以进行容量测试。最后,您可以开始将选定的用户路由到新版本的应用程序。一些公司选择“高级用户”首先点击应用程序的新版本。您甚至可以同时在生产中使用多个版本的应用程序,根据需要将不同的用户组路由到不同的版本。

Like blue-green deployments, you need to initially deploy the new version of the application to a set of servers where no users are routed to. You can then do smoke tests and, if desired, capacity tests, on the new version. Finally, you can start to route selected users to the new version of the application. Some companies select “power users” to hit the new version of the application first. You can even have multiple versions of your application in production at the same time, routing different groups of users to different versions as required.

金丝雀发布有几个好处:

There are several benefits to canary releasing:

1. 回滚变得容易:只要停止将用户路由到错误的版本,您就可以在闲暇时调查日志。

1. It makes rolling back easy: Just stop routing users to the bad version, and you can investigate the logs at your leisure.

2. 您可以通过将一些用户路由到新版本而将一些用户路由到旧版本,将其用于 A/B 测试。一些公司衡量新功能的使用情况,如果没有足够的人使用它们,就将其淘汰。其他人衡量新版本产生的实际收入,如果新版本的收入较低则回滚。2如果您的软件生成搜索结果,您可以比较真实用户在新版本和旧版本中获得的结果的质量。您不需要将大量用户路由到新版本来进行 A/B 测试;有代表性的样本就足够了。

2. You can use it for A/B testing by routing some users to the new version and some to the old version. Some companies measure the usage of new features, and kill them if not enough people use them. Others measure the actual revenue generated by the new version, and roll back if the revenue for the new version is lower.2 If your software generates search results, you might compare the quality of the results obtained by real users in the new version versus the old version. You don’t need to route a large number of users to the new version to do A/B testing; a representative sample is sufficient.

3. 您可以通过逐渐增加负载、慢慢地将越来越多的用户路由到应用程序并测量应用程序的响应时间和指标(如 CPU 使用率、I/O 和内存使用率)来检查应用程序是否满足容量要求,并观察日志中的异常。如果您的生产环境太大而无法创建逼真的类似生产的容量测试环境,这是一种风险相对较低的容量测试方法。

3. You can check if the application meets capacity requirements by gradually ramping up the load, slowly routing more and more users to the application and measuring the application’s response time and metrics like CPU usage, I/O, and memory usage, and watching for exceptions in logs. This is a relatively low-risk way to test capacity if your production environment is too large to create a realistic production-like capacity testing environment.

主题也有一些变化。金丝雀发布并不是进行 A/B 测试的唯一方法——相反,您可以在应用程序中使用开关将不同的用户路由到不同的行为。或者,您可以使用运行时配置设置来更改行为。但是,这些替代方案不提供金丝雀发布的其他好处。

There are also some variations on the theme. Canary releasing is not the only way to do A/B testing—you can, instead, use switches in your application to route different users to different behavior. Alternatively, you could use runtime configuration settings to change behavior. However, these alternatives don’t provide the other benefits of canary releasing.

不过,金丝雀发布并不适合所有人。如果用户在自己的计算机上安装了您的软件,则很难使用它。这个问题有一个解决方案(用于网格计算)——使您的客户端软件或桌面应用程序能够自动更新到服务器托管的已知良好版本。

Canary releasing is not for everyone, though. It is harder to use it where the users have your software installed on their own computers. There is a solution to this problem (one used in grid computing)—enable your client software or desktop application to automatically update itself to a known-good version hosted by your servers.

金丝雀发布对数据库升级施加了进一步的限制(这也适用于其他共享资源,例如共享会话缓存或外部服务):任何共享资源都需要与您希望在生产中拥有的应用程序的所有版本一起使用。另一种方法是使用无共享架构,其中每个节点真正独立于其他节点,没有共享数据库或服务,3或两种方法的某种混合。

Canary releasing imposes further constraints on database upgrades (which also apply to other shared resources, such as shared session caches or external services): Any shared resource needs to work with all versions of the application you want to have in production. The alternative approach is to use a shared-nothing architecture where each node is truly independent of other nodes, with no shared database or services,3 or some hybrid of the two approaches.

最后,在生产环境中保留尽可能少的应用程序版本很重要——尽量将其限制为两个。支持多个版本很痛苦,因此请将 canary 的数量保持在最低限度。

Finally, it is important to keep as few versions of your application in production as possible—try to limit it to two. Supporting multiple versions is painful, so keep the number of canaries to a minimum.

紧急修复

Emergency Fixes

在每个系统中,都会有一个关键缺陷被发现并且必须尽快修复的时刻。在这种情况下,最重要的是要记住:在任何情况下都不要破坏您的流程。紧急修复必须经过与任何其他更改相同的构建、部署、测试和发布过程。我们为什么这样说?因为我们已经看到很多通过直接登录生产环境并进行不受控制的更改来进行修复的情况。

In every system, there comes a moment when a critical defect is discovered and has to be fixed as soon as possible. In this situation, the most important thing to bear in mind is: Do not, under any circumstances, subvert your process. Emergency fixes have to go through the same build, deploy, test, and release process as any other change. Why do we say this? Because we have seen so many occasions where fixes were made by logging directly into production environments and making uncontrolled changes.

这有两个不幸的后果。首先是更改没有经过适当的测试,这可能导致无法解决问题甚至可能加剧问题的回归和补丁。其次,更改通常不会被记录(即使记录了,为解决您在第一次更改中引入的问题而进行的第二次和第三次更改也不会被记录)。因此,环境最终处于未知状态,无法重现,并以无法管理的方式破坏进一步的部署。

This has two unfortunate consequences. The first is that the change is not tested properly, which can lead to regressions and patches that do not fix the problem and may even exacerbate it. Secondly, the change is often not recorded (and even if it is, the second and third changes made to fix the problems you introduced with the first change do not get recorded). Hence the environment ends up in an unknown state that makes it impossible to reproduce, and breaks further deployments in unmanageable ways.

这个故事的寓意是:通过标准部署管道运行每个紧急修复程序。这只是保持低周期时间的另一个原因。

The moral of the story is: Run every emergency fix through your standard deployment pipeline. This is just one more reason to keep your cycle time low.

有时实际上不值得通过紧急修复来修复缺陷。您应该始终考虑缺陷影响了多少人、发生的频率以及缺陷对用户的影响有多严重。如果缺陷影响的人很少、发生的频率低且影响小,那么如果与部署新版本相关的风险相对较高,则立即修复它可能没有意义。当然,这是通过有效的配置管理和自动化部署过程来降低与部署相关的风险的一个很好的论据。

Sometimes it is not actually worth fixing a defect through an emergency fix. You should always consider how many people the defect affects, how often it occurs, and how severe the defect is in terms of its impact on users. If the defect affects few people, occurs infrequently, and has a low impact, it may not make sense to fix it immediately if the risks associated with deploying a new version are relatively high. Of course this is a great argument for reducing the risks associated with deployment through effective configuration management and an automated deployment process.

如前所述,进行紧急修复的一种替代方法是回滚到先前已知的良好版本。

One alternative to making an emergency fix is to roll back to the previous known good version, as described earlier.

以下是处理生产缺陷时需要考虑的一些注意事项:

Here are some considerations to take into account when dealing with a defect in production:

• 切勿在深夜进行,并始终与其他人配对。

• Never do them late at night, and always pair with somebody else.

• 确保您已经测试了您的紧急修复过程。

• Make sure you have tested your emergency fix process.

• 只有在极端情况下才能规避对您的应用程序进行更改的常规流程。

• Only under extreme circumstances circumvent the usual process for making changes to your application.

• 确保您已经测试过使用暂存环境进行紧急修复。

• Make sure you have tested making an emergency fix using your staging environment.

• 有时回滚到以前的版本比部署修复更好。做一些分析找出最好的解决方案。考虑一下如果您丢失数据或面临集成或编排问题会发生什么。

• Sometimes it’s better to roll back to the previous version than to deploy a fix. Do some analysis to work out what the best solution is. Consider what happens if you lose data or face integration or orchestration problems.

持续部署

Continuous Deployment

遵循极限编程的座右铭——如果它有伤害,就更频繁地做——逻辑上的极端是将通过自动化测试的每一个更改部署到生产中。这种技术被称为持续部署,这是一个由 Timothy Fitz [aJA8lN] 推广的术语。当然不仅仅是持续部署(我可以随心所欲地持续部署到UAT:没什么大不了的)。关键是它是持续部署到生产中。

Following the motto of Extreme Programming—if it hurts, do it more often—the logical extreme is to deploy every change that passes your automated tests to production. This technique is known as continuous deployment, a term popularized by Timothy Fitz [aJA8lN]. Of course it’s not just continuous deployment (I can continuously deploy to UAT all I like: no big deal). The crucial point is that it is continuous deployment to production.

这个想法很简单:我采用我的管道并使最后一步——部署到生产——自动化。这样,如果签入通过了所有自动化测试,它就会直接部署到生产环境中。为了不至于造成破坏,您的自动化测试必须非常出色——应该有涵盖整个应用程序的自动化单元测试、组件测试和验收测试(功能性和非功能性)。您必须首先编写所有测试(包括验收测试),这样只有当故事完成时,签入才会通过验收测试。

The idea is simply this: I take my pipeline and make the final step—deployment to production—automatic. That way, if a check-in passes all the automated tests, it gets deployed directly to production. In order for this not to cause breakages, your automated tests have to be fantastic—there should be automated unit tests, component tests, and acceptance tests (functional and nonfunctional) covering your entire application. You have to write all your tests—including acceptance tests—first, so that only when a story is complete will check-ins pass the acceptance tests.

持续部署可以与金丝雀发布相结合,方法是使用一个自动化流程,首先向一小部分用户推出新版本,一旦确定(可能是手动步骤)没有问题就向所有用户推出与新版本。一个好的金丝雀发布系统提供的额外保障使得持续部署成为一个风险更低的提议。

Continuous deployment can be combined with canary releasing by using an automated process that rolls out a new version to a small group of users first, rolling it out to all users once it has been determined (probably as a manual step) that there are no problems with the new version. The added safeguards provided by a good canary releasing system make continuous deployment an even less risky proposition.

持续部署并不适合所有人。有时,您不想立即将新功能发布到生产环境中。在对合规性有限制的公司中,部署到生产需要批准。产品公司通常必须支持他们发布的每个版本。然而,它当然有可能在很多地方发挥作用。

Continuous deployment isn’t for everyone. Sometimes, you don’t want to release new features into production immediately. In companies with constraints on compliance, approvals are required for deployments to production. Product companies usually have to support every release they put out. However, it certainly has the potential to work in a great many places.

对持续部署的直觉反对是它风险太大。但是,正如我们之前所说,更频繁的发布会降低发布任何特定版本的风险。这是真的,因为版本之间的变化量下降了。因此,如果您发布每个更改,则风险量仅限于该更改固有的风险。持续部署是降低任何特定版本风险的好方法。

The intuitive objection to continuous deployment is that it is too risky. But, as we have said before, more frequent releases lead to lower risk in putting out any particular release. This is true because the amount of change between releases goes down. So, if you release every change, the amount of risk is limited just to the risk inherent in that one change. Continuous deployment is a great way to reduce the risk of any particular release.

也许最重要的是,持续部署会迫使您做正确的事情(正如 Fitz 在他的博客文章中指出的那样)。如果不自动化整个构建、部署、测试和发布过程,您就无法做到这一点。如果没有一套全面、可靠的自动化测试,您就无法做到这一点。如果不编写针对类似生产环境运行的系统测试,您就无法做到这一点。这就是为什么,即使您实际上不能发布通过所有测试的每组更改,您也应该致力于创建一个流程,让您在选择时可以这样做。

Perhaps most importantly, continuous deployment forces you to do the right thing (as Fitz points out in his blog post). You can’t do it without automating your entire build, deploy, test, and release process. You can’t do it without a comprehensive, reliable set of automated tests. You can’t do it without writing system tests that run against a production-like environment. That’s why, even if you can’t actually release every set of changes that passes all your tests, you should aim to create a process that would let you do so if you choose to.

您的作者真的很高兴看到持续部署文章在软件开发社区引起如此轰动。它强化了我们多年来一直在谈论的发布过程。部署管道都是为了创建一个可重复、可靠、自动化的系统,以尽快将更改投入生产。它是关于使用最高质量的流程创建最高质量的软件,并在此过程中大大降低发布过程的风险。持续部署采用这种方法得出其合乎逻辑的结论。它应该被认真对待,因为它代表了软件交付方式的范式转变。即使您有充分的理由不发布您所做的每项更改——而且这样的理由比您想象的要少——您应该表现得好像您打算这样做一样。

Your authors were really delighted to see the continuous deployment article cause such a stir in the software development community. It reinforces what we’ve been saying about the release process for years. Deployment pipelines are all about creating a repeatable, reliable, automated system for getting changes into production as fast as possible. It is about creating the highest quality software using the highest quality process, massively reducing the risks of the release process along the way. Continuous deployment takes this approach to its logical conclusion. It should be taken seriously, because it represents a paradigm shift in the way software is delivered. Even if you have good reasons for not releasing every change you make—and there are less such reasons than you might think—you should behave as if you were going to do so.

持续发布用户安装的软件

Continuously Releasing User-Installed Software

将应用程序的新版本发布到您控制的生产环境是一回事。发布用户在自己机器上安装的软件的新版本——客户端安装的软件——是另一回事。有几个问题需要考虑:

Releasing a new version of your application to a production environment you control is one thing. Releasing a new version of software installed by users on their own machines—client-installed software—is another. There are several issues to consider:

• 管理升级体验

• Managing the upgrade experience

• 迁移二进制文件、数据和配置

• Migrating binaries, data, and configuration

• 测试升级过程

• Testing the upgrade process

• 从用户那里获取崩溃报告

• Getting crash reports from users

客户端安装软件的一个严重问题是管理软件的大量版本,随着时间的推移,这些版本最终会被滥用。这可能会导致支持方面的噩梦:为了调试任何问题,您必须将源代码恢复到正确的版本,并将您的注意力重新放在那个时间点的应用程序特性以及任何已知问题上。理想情况下,您希望每个人都使用相同版本的软件:最新的稳定版本。为了实现这一点,必须让升级体验尽可能轻松。

A serious issue with client-installed software is managing the large number of versions of your software that, over time, end up in the wild. This can cause a support nightmare: In order to debug any problems, you have to revert your source to the correct version and cast your mind back to the peculiarities of the application at that point in time, along with any known issues. Ideally, you want everyone to use the same version of your software: the latest stable version. In order to achieve this, it is essential to make the upgrade experience as painless as possible.

客户端可以通过多种方式处理升级过程:

There are several ways in which clients can handle the upgrade process:

1. 让您的软件检查新版本并提示用户下载并升级到最新版本。这是最容易实现的,但使用起来最痛苦。没有人愿意看下载进度条。

1. Have your software check for new versions and prompt the user to download and upgrade to the latest version. This is the easiest to implement, but the most painful to use. Nobody wants to watch a download progress bar.

2.后台下载,提示安装。在此模型中,您的软件会在运行时定期检查更新并静默下载它们。下载成功后,一直提示用户升级到最新版本。

2. Download in the background and prompt for installation. In this model, your software periodically checks for updates while running and downloads them silently. After the download is successful, it keeps prompting the user to upgrade to the latest version.

3.后台下载,下次重启应用时静默升级。如果您想升级(如 Firefox 所做的),您的应用程序可能还会提示您立即重新启动。

3. Download in the background and silently upgrade the next time the application is restarted. Your application might also prompt you to restart now if you’d like to upgrade (as Firefox does).

如果您想保守一些,选项 1 和 2 可能看起来更有吸引力。然而,几乎在所有情况下,它们都是错误的选择。作为应用程序开发人员,您希望为用户提供选择。然而,在升级的情况下,用户不明白为什么他们可能想要延迟升级。它迫使他们考虑升级而不提供任何信息来帮助他们决定一种方式或另一种方式。因此,理性的选择通常是不升级,因为任何升级都可能破坏应用程序。

If you want to be conservative, options 1 and 2 may look more attractive. However, they are, in almost every case, the wrong choice. As an application developer, you want to give your users options. However, in the case of upgrading, users have no understanding of why they might want to delay the upgrade. It forces them to think about upgrading without providing any information to help them decide one way or the other. As a result, the rational choice is usually not to upgrade, simply because any upgrade might break the application.

事实上,完全相同的思维过程正在开发团队的头脑中进行。升级过程可能会破坏应用程序,开发团队认为,所以我们应该在这件事上给用户一个选择。但是,如果升级过程确实不稳定,用户永远不要升级当然是正确的。如果升级过程不是不稳定的,那么提供选择就没有意义:升级应该自动进行。所以事实上,给用户选择只是告诉他们开发者对升级过程没有信心。

In fact, exactly the same thought process is going on in the development team’s head. The upgrade process might break the application, thinks the development team, so we should give the user a choice on this matter. But, if the upgrade process is indeed flaky, the user would of course be correct never to upgrade. If the upgrade process is not flaky, then there is no point in providing the choice: The upgrade should happen automatically. So in fact, giving users a choice simply tells them that the developers have no confidence in the upgrade process.

正确的解决方案是让升级过程防弹——并且静默升级。特别是,如果升级过程失败,应用程序应该自动恢复到以前的版本并将失败报告给开发团队。然后他们可以解决问题并推出一个(希望)正确升级的新版本。所有这一切都可以在用户甚至不需要知道任何事情的情况下发生。提示用户的唯一充分理由是是否需要采取一些纠正措施。

The correct solution is to make the upgrade process bullet proof—and to upgrade silently. In particular, if the upgrade process fails, the application should automatically revert to the previous version and report the failure to the development team. They can then fix the problem and roll out a new version which will (hopefully) upgrade correctly. All this can happen without the user even having to know anything. The only good reason for prompting the user is if there is some corrective action that needs to be taken.

当然,您可能不希望软件静默升级是有原因的。也许您不希望它给家里打电话,或者您是公司网络运营团队的一员,该网络只允许在新版本的应用程序与其余已批准的应用程序进行详尽测试后部署,以确保坚如磐石的桌面。这些都是合理的用例,它们可以通过配置选项来关闭自动升级。

Of course, there are reasons why you might not want your software to silently upgrade. Perhaps you don’t want it to phone home, or you are part of the operations team of a corporate network which only allows the new versions of applications to be deployed after they have been exhaustively tested with the rest of the approved applications to ensure a rock-solid desktop. These are both reasonable use cases, and they can be accommodated with a configuration option to turn off automatic upgrades.

为了提供坚如磐石的升级体验,您需要处理迁移二进制文件、数据和配置。在每种情况下,升级过程都应保留旧版本的副本,直到完全确定升级成功为止。如果升级失败,它应该静默恢复旧的二进制文件、数据和配置。一种简单的方法是在安装目录中有一个包含所有这些东西的当前版本的目录,并用新的版本创建一个新目录。然后,切换版本只是重命名目录或在某处放置对当前版本目录的引用(在 UNIX 系统上,这通常使用符号链接来完成)。

In order to provide a rock-solid upgrade experience, you need to handle migrating binaries, data, and configuration. In each case, the upgrade process should keep copies of the old ones around until it is absolutely sure the upgrade has been successful. If the upgrade fails, it should restore the old binaries, data, and configuration silently. One easy way to do this is to have a directory inside the install directory with the current versions of all of these things, and to create a new directory with the new ones. Then, switching versions is simply a matter of renaming directories or putting a reference to the current version’s directory somewhere (on UNIX systems, this is commonly accomplished using symbolic links).

您的应用程序应该能够从任何版本升级到任何其他版本。为此,您需要对数据存储和配置文件进行版本控制。每次更改数据存储或配置的模式时,都需要创建一个脚本将它们从一个版本前滚到下一个版本,如果要支持降级,还需要创建一个脚本从新版本回滚到新版本旧版。然后,当您的升级脚本运行时,它会确定数据存储和配置的当前版本,并应用相关脚本将它们迁移到最新版本。第 12 章“管理数据”中更详细地描述了该技术。

Your application should be able to upgrade from any version to any other version. In order to do this, you need to version your data store and your configuration file. Every time you change the schema of your data store or your configuration, you need to create a script to roll them forward from one version to the next and, if you want to support downgrades, a script to roll back from the new version to the old version. Then, when your upgrade script runs, it determines the current version of the data store and configuration, and applies the relevant scripts to migrate them to the latest version. This technique is described in much more detail in Chapter 12, “Managing Data.”

您应该将升级过程作为部署管道的一部分进行测试。您可以为此目的在您的管道中设置一个阶段,该阶段采用来自友好用户的真实数据和配置的初始状态选择,并运行到最新版本的升级。这应该在有代表性的目标机器上自动完成。

You should test the upgrade process as part of your deployment pipeline. You can have a stage in your pipeline just for this purpose, which takes a selection of initial states with real data and configuration, taken from friendly users, and runs the upgrade to the latest version. This should be done automatically on a representative selection of target machines.

最后,客户端安装的软件必须能够将崩溃报告给开发团队。在他关于客户端软件持续部署的博客文章 [amYycv] 中,Timothy Fitz 描述了客户端软件遇到的范围广泛的恶意事件:“损坏的硬件、内存不足的情况、外语操作系统、随机 DLL、其他进程插入他们的编码到你的,司机争取在发生碰撞时首先采取行动,以及其他越来越深奥和不可预测的集成问题。”

Finally, it is essential for client-installed software to be able to report crashes back to the development team. In his blog entry on continuous deployment for client software [amYycv], Timothy Fitz describes the wide range of hostile events encountered by client software: “broken hardware, out-of-memory conditions, foreign-language operating systems, random DLLs, other processes inserting their code into yours, drivers fighting for first-to-act in the event of crashes, and other progressively more esoteric and unpredictable integration issues.”

这使得崩溃报告框架变得必不可少。Google 在 Windows 上开源了其用于 C++ 的框架 [b84QtM],如果需要,可以从 .NET 内部调用它。关于如何做好崩溃报告以及报告哪些指标有用的讨论取决于您使用的技术堆栈,这超出了本书的范围。Fitz 的博客条目提供了一些有用的讨论作为起点。

This makes a crash reporting framework essential. Google has open-sourced its framework [b84QtM] for C++ on Windows, which can be called from inside .NET if required. A discussion of how to do crash reporting well and what metrics it is useful to report depends on the technology stack you are using and is beyond the scope of this book. Fitz’ blog entry provides some useful discussion as a starting point.

技巧和窍门

Tips and Tricks

执行部署的人员应参与创建部署过程

The People Who Do the Deployment Should Be Involved in Creating the Deployment Process

通常,部署团队被要求部署他们没有参与开发的系统。他们得到一张 CD 和一叠影印文件,上面有模糊的说明,例如“安装 SQL Server”。

Often, deployment teams are asked to deploy systems that they have not had any hand in developing. They are given a CD and a sheaf of photocopied papers, with vague instructions like “Install SQL Server.”

这种事情是运维和开发团队关系不好的表现,可以肯定的是,真正部署到生产的时候,过程会很痛苦,很长,互相指责,脾气暴躁。

This kind of thing is symptomatic of a bad relationship between operations and the development teams, and it is certain that when it comes to the actual deployment to production, the process will be painful and drawn out with many recriminations and short tempers.

开发人员在开始一个项目时应该做的第一件事就是非正式地寻找操作人员并让他们参与开发过程。这样一来,运维人员从一开始就参与到软件中,双方都知道,并且在发布前很久就实践过很多次,这样就会像新生儿一样顺利宝宝的屁股。

The first thing developers should do when starting a project is to seek out the operations people informally and involve them in the development process. That way, the operations people will have been involved in the software from the very beginning, and both sides will know, and have practiced many times, exactly what is going to happen long before the release, which will thus be as smooth as a newborn baby’s bottom.

记录部署活动

Log Deployment Activities

如果您的部署过程不是完全自动化的,包括环境供应,那么记录您的自动化部署过程复制或创建的所有文件很重要。这样,就可以轻松调试发生的任何问题——您确切地知道在哪里查找配置信息、日志和二进制文件。

If your deployment process isn’t completely automated, including the provisioning of environments, it is important to log all the files that your automated deployment process copies or creates. That way, it’s easy to debug any problems that occur—you know exactly where to look for configuration information, logs, and binaries.

同样,重要的是要保留环境中每个硬件的清单、部署期间接触的部分以及实际部署的日志。

In the same way, it is important to keep a manifest of every piece of hardware in your environments, which bits you touched during deployment, and the logs of actual deployments.

不要删除旧文件,移动它们

Don’t Delete the Old Files, Move Them

进行部署时,请确保保留一份先前版本的副本。然后,确保在部署新版本之前清除旧文件。如果来自旧部署的杂散文件仍然存在于新部署的版本中,它可能会导致难以追踪的错误。在最坏的情况下,它可能会导致数据损坏,例如,如果旧的管理界面页面留在原处。

When you do a deployment, make sure you keep a copy of the previous version around. Then, ensure that you clear out the old files before deploying the new version. If a stray file from the old deployment is still lying around in the newly deployed version, it can cause hard-to-track-down bugs. At worst, it could lead to corrupted data if, for example, an old administration interface page is left in place.

在 UNIX 世界中,一个好的做法是将应用程序的每个版本部署到一个新目录中,并有一个指向当前版本的符号链接。部署和回滚版本只是将符号链接更改为指向先前版本的问题。网络版本是让不同的版本位于不同的服务器或同一服务器上的不同端口范围。正如我们在第 261 页的“蓝绿部署”部分中所述,使用反向代理在它们之间切换

A good practice in the UNIX world is to deploy each version of the application into a new directory and have a symbolic link that points to the current version. Deploying and rolling back versions is simply a matter of changing the symbolic link to point to the previous version. The network version of this is to have different versions sitting on different servers or different port ranges on the same server. Switch between them using a reverse proxy, as we describe in the “Blue-Green Deployments” section on page 261.

部署是整个团队的责任

Deployment Is the Whole Team’s Responsibility

“构建和部署专家”是一种反模式。团队的每个成员都应该知道如何部署,团队的每个成员都应该知道如何维护部署脚本。这可以通过确保每次构建软件(即使是在开发人员机器上)时使用真实的部署脚本来实现。

A “build and deployment expert” is an antipattern. Every member of the team should know how to deploy, and every member of the team should know how to maintain the deployment scripts. This can be achieved by making sure that every time you build the software, even on a developers machine, it uses the real deployment scripts.

损坏的部署脚本应该会破坏构建。

A broken deployment script should break the build.

服务器应用程序不应该有 GUI

Server Applications Should Not Have GUIs

过去常常看到带有 GUI 的服务器应用程序。这在 PowerBuilder 和 Visual Basic 应用程序中尤为常见。这些应用程序通常有我们提到的其他问题,例如不可编写脚本的配置、对安装位置敏感的应用程序等。不过,主要问题是要工作,机器必须有一个用户登录并显示用户界面。这意味着重新启动,无论是意外的还是由于升级,都会注销用户,并且服务器将停止。然后支持工程师必须登录机器并手动启动服务器。

It used to be common to see server applications with GUIs. This was particularly common with PowerBuilder and Visual Basic applications. These applications often had other problems we have mentioned, such as configuration that is not scriptable, applications that are sensitive to where they are installed, etc. The main problem, though, was that to work, the machine must have had a user logged in and the UI showing. This means that a reboot, either accidental or due to upgrades, will log the user out, and the server will stop. A support engineer would then have to log into the machine and manually start the server.

为新部署预热

Have a Warm-Up Period for a New Deployment

不要在预定时间打开您的 eBay 杀手级网站。到站点正式“上线”时,它应该已经运行了一段时间,足以让应用程序服务器和数据库填满它们的缓存、建立所有连接并“预热”。

Don’t switch on your eBay-killer website at the prearranged hour. By the time the site is officially “live,” it should have been running for some time, long enough for the application servers and databases to fill their caches, make all their connections, and “warm up.”

对于网站,这可以通过金丝雀发布来实现。您的新服务器和新版本可以从服务一小部分请求开始;然后,当环境稳定并得到验证时,您可以将更多负载转移到新系统上。

With websites, this can be accomplished through canary releasing. Your new servers and new release can start by serving some small proportion of requests; then, when the environment is bedded in and proven, you can switch more load over to the new system.

许多应用程序都有在部署时急切填充的内部缓存。在缓存满之前,应用程序的响应时间通常很差,甚至可能会失败。如果您的应用程序表现得像这样,请确保在您的部署计划中记下它,包括填充缓存所需的时间长度(您当然会在类似生产的环境中测试过)。

Many applications have internal caches that are eagerly filled at deployment time. Until the caches are full, the application will often have a poor response time and may even fail. If your application behaves like this, ensure you make a note of it in your deployment plan, including the length of time it takes to fill the cache (which you will of course have tested on a production-like environment).

快速失败

Fail Fast

部署脚本应该包含测试以确保部署成功。这些应该作为部署本身的一部分运行。它们不应该是全面的单元测试,而应该是确保部署的单元正常工作的简单冒烟测试。

Deployment scripts should incorporate tests to ensure that the deployment was successful. These should be run as part of the deployment itself. They shouldn’t be comprehensive unit tests, but simple smoke tests that make sure the deployed units are working.

理想情况下,系统应该在初始化时执行这些检查,如果遇到错误,它应该无法启动。

Ideally, the system should perform these checks as it initializes, and if it encounters an error, it should fail to start.

不要直接在生产环境上进行更改

Don’t Make Changes Directly on the Production Environment

生产环境中的大多数停机时间都是由不受控制的更改引起的。生产环境应该完全锁定,这样只有您的部署管道才能对其进行更改。这包括从环境配置到部署在其上的应用程序及其数据的所有内容。许多组织都有严格的访问管理流程。我们已经看到用于管理生产访问的方案包括由批准过程创建的有限生命密码和需要从 RSA fob 输入代码的两阶段身份验证系统。在一个组织中,对生产的更改只能通过闭锁房间内的终端进行授权,并通过闭路电视摄像机监控屏幕。

Most downtime in production environments is caused by uncontrolled changes. Production environments should be completely locked down, so that only your deployment pipeline can make changes to it. That includes everything from the configuration of the environment to the applications deployed on it and their data. Many organizations have strict access management processes in place. Schemes that we have seen used to manage access to production include limited-lifetime passwords created by an approval process and two-phase authentication systems that require a code to be typed in from an RSA fob. In one organization, changes to production could only be authorized from a terminal in a locked room with a CCTV camera monitoring the screen.

这些授权过程应该融入到您的部署管道中。这样做会给您带来相当大的好处:这意味着您对生产所做的每项更改都有一个记录系统。没有比准确记录对生产进行的更改、更改时间以及授权人更好的审计跟踪了。部署管道正是提供了这样的设施。

These authorization processes should be baked into your deployment pipeline. Doing so gains you a considerable benefit: It means that you have a system of record for every change made to production. There is no better audit trail than a record of exactly which change was made to production, when, and who authorized it. The deployment pipeline provides exactly such a facility.

概括

Summary

部署管道的后期阶段都与部署到测试和生产环境有关。这些阶段与管道的前几个阶段不同,因为没有自动化测试作为后阶段的一部分运行。这意味着这些阶段不会通过或失败。但它们仍然是管道中不可或缺的一部分。只要提供正确的凭据,您的实施应该能够将通过自动化测试的应用程序的任何版本部署到您的任何环境中,只需按下一个按钮。您团队中的每个人都应该能够准确地看到部署在何处的内容,以及该版本中包含的更改。

The latter stages of the deployment pipeline are all concerned with deploying into testing and production environments. These stages are different from the previous stages of the pipeline in that there are no automated tests run as part of the latter stages. That means these stages don’t pass or fail. But they still form an integral part of the pipeline. Your implementation should make it possible to deploy any version of your application that has made it past the automated tests into any of your environments at the push of a button, given the correct credentials. It should be possible for everyone on your team to see exactly what is deployed where, and what changes are included in that version.

降低发布风险的最佳方法当然是排练它们。将应用程序发布到各种测试环境中的频率越高越好。具体来说,您将应用程序首次发布到新测试环境的频率越高,您的流程就越可靠,在生产发布中遇到问题的可能性就越小。您的自动化部署系统应该能够从头开始调试新环境,以及更新预先存在的环境。

The best way to lower the risk of your releases is, of course, to rehearse them. The more frequently you release the application into a variety of test environments, the better. Specifically, the more frequently you release the application into new test environments for the first time, the more reliable your process will be and the less likely you are to encounter a problem in a production release. Your automated deployment system should be able to commission a new environment from scratch, as well as update a preexisting environment.

然而,对于任何规模和复杂性的系统,首次发布到生产中总是一个重要时刻。充分考虑流程并对其进行充分计划以使其尽可能简单是至关重要的。无论您的团队多么敏捷,发布策略都是软件项目的其中一个方面,在这些方面中,做出决策的最后责任时刻不是发布前的几天,甚至不是几次迭代。这应该是您计划的一部分,并且至少在一定程度上影响您在项目生命周期早期的开发决策。发布策略将而且应该随着时间的推移而发展,随着首次发布时间的临近变得更加准确和详细。

Nevertheless, for a system of any size and complexity, the first release into production will always be a momentous occasion. It is vital to have thought about the process and planned for it sufficiently to make it as straightforward as possible. However agile your team, the release strategy is one of those aspects of the software project where the last responsible moment to make decisions is not a few days, or even a few iterations, before the release. This should be part of your planning and, at least in part, be influencing your development decisions from early on in the life of the project. The release strategy will, and should, evolve over time, becoming more accurate and more detailed as the time of the first release approaches.

发布计划最关键的部分是召集组织中参与交付的每个部分的代表:构建、基础设施和运营团队、开发团队、测试人员、DBA 和支持人员。这些人应该在项目的整个生命周期中不断开会,并不断努力使交付过程更有效率。

The most crucial part of release planning is assembling representatives from every part of your organization involved in delivery: build, infrastructure, and operations teams, development teams, testers, DBAs, and support personnel. These people should continue to meet throughout the life of the project and continually work to make the delivery process more efficient.

第三部分:交付生态系统

Part III: The Delivery Ecosystem

第 11 章管理基础设施和环境

Chapter 11. Managing Infrastructure and Environments

介绍

Introduction

正如我们在第 1 章中描述的那样,部署软件分为三个步骤:

As we describe in Chapter 1, there are three steps to deploying software:

• 创建和管理您的应用程序将在其中运行的基础架构(硬件、网络、中间件和外部服务)

• Creating and managing the infrastructure in which your application will run (hardware, networking, middleware, and external services)

• 安装正确版本的应用程序

• Installing the correct version of your application into it

• 配置应用程序,包括它需要的任何数据或状态

• Configuring the application, including any data or state that it requires

本章介绍这些步骤中的第一步。由于我们的目标是所有测试环境(包括持续集成环境)都应该像生产环境一样,特别是在它们的管理方式上,因此本章还将通过扩展涵盖测试环境的管理。

This chapter deals with the first of these steps. Since our goal is that all testing environments (including continuous integration environments) should be production-like, particularly in the way they are managed, this chapter will also, by extension, cover the management of testing environments.

让我们首先定义在此上下文中环境的含义。环境是您的应用程序运行所需的所有资源及其配置。以下属性描述了环境:

Let’s start by defining what we mean by environment in this context. An environment is all of the resources that your application needs to work and their configuration. The following attributes describe the environment:

• 构成环境的服务器的硬件配置(例如 CPU 的数量和类型、内存量、主轴、NIC 等)以及连接它们的网络基础设施

• The hardware configuration of the servers that form the environment (such as the number and type of CPUs, amount of memory, spindles, NICs, and so on) and the networking infrastructure that connects them

• 支持将在其中运行的应用程序所需的操作系统和中间件(例如消息系统、应用程序和 Web 服务器、数据库服务器)的配置

• The configuration of the operating system and middleware (such as messaging systems, application and web servers, database servers) required to support the applications that will run within it

通用术语基础架构代表您组织中的所有环境,以及支持它们的服务,例如 DNS 服务器、防火墙、路由器、版本控制存储库、存储、监控应用程序、邮件服务器等。实际上,应用程序环境与组织基础架构的其余部分之间的界限可能非常明确(例如,在嵌入式软件的情况下)到极其模糊(在面向服务的体系结构的情况,其中许多基础设施被应用程序共享和依赖)。

The general term infrastructure represents all environments in your organization, along with the services that support them, such as DNS servers, firewalls, routers, version control repositories, storage, monitoring applications, mail servers, and so on. Indeed, the boundary between an application’s environment and the rest of your organization’s infrastructure can vary from very clearly defined (in the case of embedded software, for example) to extremely fuzzy (in the case of service-oriented architectures, in which much infrastructure is shared and relied upon by applications).

为部署准备环境并在部署后对其进行管理的过程是本章的主要重点。然而,实现这一点的是基于以下原则的整体方法来管理所有基础设施。1个

The process of preparing environments for deployment and managing them after deployment is the main focus of this chapter. However what enables this is a holistic approach to managing all infrastructure, based upon the following principles.1

• 应通过版本控制配置来指定基础设施的理想状态。

• The desired state of your infrastructure should be specified through version-controlled configuration.

• 基础架构应该是自主的——也就是说,它应该自动将自身更正到所需的状态。

• Infrastructure should be autonomic—that is, it should correct itself to the desired state automatically.

• 您应该始终通过检测和监控了解基础架构的实际状态。

• You should always know the actual state of your infrastructure through instrumentation and monitoring.

虽然基础设施应该是自主的,但它也应该易于重新创建,这样一来,例如在硬件出现故障的情况下,您可以快速重新建立一个新的已知良好的配置。这意味着基础设施配置也应该是一个自动化过程。这种自动配置和自主维护的结合确保了在发生故障时可以在可预测的时间内重建基础设施。

While infrastructure should be autonomic, it is also essential that it should be simple to re-create, so that, in the case of a hardware failure for example, you can quickly reestablish a new known-good configuration. This means that infrastructure provisioning should also be an automated process. This combination of automated provisioning and autonomic maintenance ensures that infrastructure can be rebuilt in a predictable amount of time in the event of failure.

有几件事需要仔细管理,以降低部署到任何类似生产环境的风险:

There are several things that need to be managed carefully to reduce the risk of deployment to any production-like environment:

• 用于测试和生产环境的操作系统及其配置

• The operating system and its configuration, for both testing and production environments

• 中间件软件栈及其配置,包括应用服务器、消息系统和数据库

• The middleware software stack and its configuration, including application servers, messaging systems, and databases

• 基础设施软件,例如版本控制存储库、目录服务和监控系统

• Infrastructural software, such as version control repositories, directory services, and monitoring systems

• 外部集成点,例如外部系统和服务

• External integration points, such as external systems and services

• 网络基础设施,包括路由器、防火墙、交换机、DNS、DHCP 等

• Network infrastructure, including routers, firewalls, switches, DNS, DHCP, and so on

• 应用程序开发团队和基础架构管理团队之间的关系

• The relationship between the application development team and the infrastructure management team

我们将从列表中的最后一项开始。在这个其他技术性的枚举中,它可能看起来断章取意。然而,如果这两个团队紧密合作解决问题,其他一切都会变得容易得多。他们应该从项目开始就在环境管理和部署的所有方面进行协作。

We shall start with the last item in the list. It may seem out of context in this otherwise technical enumeration. However, everything else becomes a great deal easier if these two teams work closely together to solve problems. They should collaborate on all aspects of environment management and deployment from the beginning of the project.

这种对协作的关注是 DevOps 运动的核心原则之一,旨在为系统管理和 IT 运营领域带来一种敏捷方法。该运动的另一个核心原则是敏捷技术可以有效地用于管理基础设施。本章讨论的许多技术,例如自主基础设施和行为驱动的监控,都是由参与创建该运动的人们开发的。

This focus on collaboration is one of the central principles of the DevOps movement, which aims to bring an agile approach to the world of system administration and IT operations. The other core principle of the movement is that agile techniques can be usefully brought to bear on managing infrastructure. Many of the techniques discussed in this chapter, such as autonomic infrastructure and behavior-driven monitoring, were developed by people involved in founding this movement.

在阅读本章时,请牢记测试环境应该类似于生产环境的指导原则。这意味着它们在上面列出的大多数技术方面应该是相似的(尽管不一定相同)。目标是及早发现环境问题,并在投入生产之前演练部署和配置等关键活动,以降低发布风险。测试环境应该足够相似才能实现这一点。至关重要的是,管理它们的技术应该是相同的。

As you read this chapter, keep in mind the guiding principle that testing environments should be production-like. This means that they should be similar (although not necessarily identical) in most of the technical aspects listed above. The goal is to catch environmental problems early and to rehearse critical activities like deployment and configuration before you get to production, so as to reduce the risk of releases. Test environments should be similar enough to achieve this. Crucially, the techniques to manage them should be identical.

这种方法可能很辛苦,而且可能很昂贵,但有一些工具和技术可以提供帮助,例如虚拟化和自动化数据中心管理系统。这种方法的好处是如此之大,就在开发过程的早期发现模糊且难以重现的配置和集成问题而言,您的努力将得到很多倍的回报。

This approach can be hard work and potentially expensive, but there are tools and techniques to help, such as virtualization and automated data center management systems. The benefits of this approach are so great, in terms of catching obscure and hard-to-reproduce configuration and integration problems early in the development process, that your effort will be repaid many times over.

最后,虽然本章假定您的应用程序的生产环境处于运营团队的控制之下,但原则和问题对于软件产品是相同的。例如,虽然一个软件产品不一定有人定期备份它的数据,但数据恢复对任何用户来说都是一个重要的关注点。这同样适用于其他非功能性需求,例如可恢复性、可支持性和可审计性。

Finally, although this chapter assumes that your application’s production environment is under the control of an operations team, the principles and issues are the same for software products. For example, although a software product doesn’t necessarily have someone backing up its data regularly, data recovery will be an important concern for any user. The same applies to other nonfunctional requirements such as recoverability, supportability, and auditability.

了解运营团队的需求

Understanding the Needs of the Operations Team

不言而喻,大多数项目的失败是由于人的问题而不是技术问题。在将代码部署到测试和生产环境时,没有比这更真实的了。几乎所有中型和大型公司都将开发和基础设施管理(或众所周知的运营)活动分成不同的组或孤岛。2通常情况下,这两组利益相关者之间的关系不稳定。这是因为开发团队被激励尽快交付软件,而运营团队的目标是稳定性。

It is axiomatic that most projects fail due to people problems rather than technical problems. Nowhere is this more true than when it comes to deploying code to testing and production environments. Almost all medium and large companies separate the activities of development and infrastructure management (or operations as it is often known) into different groups or silos.2 It is often the case that these two groups of stakeholders have an uneasy relationship. This is because development teams are incentivized to deliver software as rapidly as possible, whereas operations teams aim for stability.

可能要记住的最重要的事情是所有利益相关者都有一个共同的目标:使有价值的软件的发布成为一项低风险的活动。根据我们的经验,我们发现最好的方法是发布为尽可能频繁(因此持续交付)。这确保了版本之间的变化尽可能少。如果你在一个发布需要几天时间、不眠之夜和长时间工作的组织工作,你无疑会对这个想法感到恐惧。我们的回应是,发布可以而且应该是一项可以在几分钟内完成的活动。这似乎不现实。然而,我们已经看到许多大公司的大型项目,其中发布已经从甘特图驱动的睡眠剥夺实验变成了一天几次在几分钟内执行的低风险活动。

Probably the most important thing to keep in mind is that all stakeholders have a common goal: making the release of valuable software a low-risk activity. In our experience, we have found that the best way to do this is by releasing as frequently as possible (hence continuous delivery). This ensures that there is as little change as possible between releases. If you work in an organization where releases take several days, with sleepless nights and long working hours, you will no doubt recoil in horror at this idea. Our response is that releasing can and should be an activity that can be performed in a few minutes. This may seem unrealistic. However, we have seen many large projects in large companies where release has gone from a sleep deprivation experiment driven by Gantt charts to a low-risk activity performed in minutes several times a day.

在小型组织中,开发团队通常负责运营。但是,在大多数中型和大型组织中,这些都是独立的组。每个人都有自己的报告线:将有一名运营主管和一名软件开发主管。每次发布产品时,这些团队及其经理都会努力确保出现的任何问题都不是他们的错。这显然是各团体之间关系紧张的潜在原因。每个小组都希望将部署风险降至最低,但每个小组都有自己的方法。

In small organizations, the development team is often responsible for operations. However, in most medium and large organizations these are independent groups. Each will have its own lines of reporting: There will be a head of operations and a head of software development. Every time a production release occurs, these teams and their managers work to ensure that any problems that arise are not their fault. This is clearly a potential cause of tension between the groups. Each group wants to minimize deployment risk, but each has its own approach.

运营团队根据关键服务质量指标衡量其有效性,例如平均故障间隔时间 (MTBF) 和平均故障修复时间 (MTTR)。运营团队通常有他们必须满足的服务级别协议 (SLA)。任何变更,包括流程变更,都会影响运营团队实现这些目标和任何其他目标(例如遵守法律法规)的能力,这都代表着风险。鉴于这种情况,以下是运营团队最重要的一些高层关注点。

Operations teams measure their effectiveness in terms of the key quality-ofservice metrics such as mean time between failures (MTBF) and mean time to repair failures (MTTR). Often operations teams have service-level agreements (SLAs) they have to meet. Any change, including a change in process, which has an effect on operations teams’ ability to meet these and any other targets (such as conformance to legal regulation), represents a risk. Given this context, here are some of the most important high-level concerns of operations teams.

文件和审计

Documentation and Auditing

运营经理希望确保对他们控制的任何环境的任何更改进行记录和审核,以便在出现问题时,他们可以找到导致问题的相关更改。

Operations managers want to ensure that any change to any environment they control is documented and audited, so that, if things go wrong, they can find the relevant changes that caused the problem.

运营经理担心他们跟踪变化的能力还有其他原因;例如,遵守萨班斯-奥克斯利法案,美国立法旨在鼓励良好的企业审计和责任,以及确保环境保持一致的愿望。但主要是为了让他们能够计算出在最后已知的良好环境状态和任何破坏之间发生了什么。

There are other reasons why operations managers are concerned about their ability to track changes; for example, the conformance to Sarbanes-Oxley, the US legislation intended to encourage good corporate auditing and responsibility, and the desire to ensure that environments remain consistent. But principally it’s so that they can work out what happened between the last known good state of the environment and any breakage.

组织将拥有的最重要的流程之一是变更管理流程,该流程用于管理对任何受控环境所做的每项变更——通常运营将控制生产和类似生产的测试环境。这通常意味着任何时候任何人想要对任何测试或生产环境进行更改,都必须请求更改。许多类型的低风险配置更改可以通过操作自行完成(在 ITIL 中,这些是“标准”更改)。

One of the most important processes an organization will have in place is a change management process, which is used to manage every change made to any controlled environments—and often operations will control both production and production-like testing environments. This usually means that any time anybody wants to make a change to any testing or production environment, a change must be requested. Many types of low-risk configuration changes can be made by operations on their own (in ITIL, these are “standard” changes).

然而,部署应用程序的新版本通常是一个“正常”变更,需要变更经理批准,并由变更顾问委员会 (CAB) 提出建议。更改请求需要包含详细信息关于变更的风险和影响,以及如果失败将如何补救。该请求应该在要部署的新版本工作开始之前提交,而不是在企业期望它上线之前的几个小时。第一次完成此过程时,期望回答很多问题。

However, deploying a new version of your application will usually be a “normal” change which will require approval by the change manager, advised by the change advisory board (CAB). A request for change needs to include details on the risk and impact of the change, and how it will be remediated if it fails. The request should be submitted before work starts on the new version to be deployed, not a couple of hours before the business expects it to go live. The first time you go through this process, expect to answer a lot of questions.

软件开发团队的成员有责任熟悉运营团队已有的任何此类系统和流程,并遵守它们。确定发布软件需要遵循的过程应该是开发团队发布计划的一部分。

Member of the software development team have a responsibility to familiarize themselves with any such systems and processes that the operations team has in place, and comply with them. Identifying the procedures that need to be followed to release your software should be part of your development team’s release plan.

异常事件警报

Alerts for Abnormal Events

运营经理将拥有适当的系统来监控他们的基础架构和正在运行的应用程序,并且希望在他们管理的任何系统发生异常情况时收到警报,以便他们可以最大限度地减少停机时间。

Operations managers will have systems in place to monitor their infrastructure and the applications running, and will want to be alerted when an abnormal condition occurs in any of the systems they manage so that they can minimize any downtime.

每个运营团队都有一些监控其生产环境的方法。他们可能有 OpenNMS,或者替代品之一,例如 Nagios 或 HP 的 Operations Manager。也许他们已经创建了自己的定制监控系统。无论他们使用哪个系统,他们都希望您的应用程序挂接到它,以便他们知道任何错误情况发生的时刻,以及到哪里寻找更多详细信息以确定出了什么问题。

Every operations team has some way of monitoring their production environments. They might have OpenNMS, or one of the alternatives such as Nagios or HP’s Operations Manager. Perhaps they have created their own custom monitoring system. Whichever system they use, they will want your application to hook into it so that they know the moment any error condition occurs, and where to look for more details to determine what has gone wrong.

重要的是要在项目开始时找出运营团队希望如何监控您的应用程序,并将其纳入您的发布计划。他们想监控什么?他们希望您的日志在哪里?您的应用程序应该使用什么钩子来通知操作人员故障?

It is important to find out, right at the beginning of the project, how the operations team expects to monitor your application, and make it part of your release plan. What do they want to monitor? Where are they expecting your logs to be? What hooks should your application use to notify operations staff of malfunctions?

例如,没有经验的开发人员最常犯的编码错误之一是吞下错误。与您的运营团队进行快速交谈应该使您确信有必要将每个错误情况记录到一个众所周知的位置,并具有适当的严重性,以便他们确切地知道问题是什么。这样做的必然结果是,如果您的应用程序由于某种原因失败,操作应该很容易重新启动或重新部署它。

For example, one of the most common coding mistakes that inexperienced developers make is to swallow errors. A quick chat with your operations team should convince you of the necessity to log every error condition to a single wellknown location, with the appropriate severity, so they know exactly what the problem is. A corollary of this is that if your application fails for some reason, it should be easy for operations to restart or redeploy it.

同样,开发团队有责任确定运营团队的监控要求并将其作为发布计划的一部分。解决这些要求的最佳方法是以与任何其他要求相同的方式对待它们。从操作人员的角度积极考虑应用程序的使用——他们是重要的用户群体。您将需要更新发布计划,以在第一个发布临近时重新启动和重新部署您的应用程序。

Again, it is the development team’s responsibility to determine the operations team’s monitoring requirements and make them part of the release plan. The best way to tackle these requirements is to treat them in the same way as any other requirements. Actively consider the use of your application from the perspective of operations personnel—they are an important constituency of users. You will need to update the release plan with the procedure to restart and redeploy your application as the first release approaches.

第一次发布只是任何应用程序生命周期的开始。您的应用程序的每个新版本都会有不同的行为,包括它产生的错误和日志消息的种类,也许还有它被监控的方式。它也可能以新的方式失败。重要的是让操作人员了解情况您发布应用程序的新版本,以便他们可以为这些更改做好准备。

The first release is just the beginning of the life of any application. Every new version of your application will behave differently, including the kinds of errors and log messages it produces, and perhaps the way it is monitored. It may fail in new ways, too. It’s important to keep operations people in the loop when you release new versions of your application, so that they can prepare for these changes.

IT 服务连续性规划

IT Service Continuity Planning

运营经理将参与其组织的 IT 服务连续性计划的创建、实施、测试和维护。运营团队管理的每项服务都将有一个恢复点目标 (RPO)——衡量灾难发生前允许数据丢失的时间长度,以及一个恢复时间目标 (RTO)——灾难发生前允许的最大时间长度服务已恢复。

Operations managers will be involved in the creation, implementation, testing, and maintenance of their organization’s IT service continuity plan. Each service the operations team manages will have a recovery point objective (RPO)—a measure of the length of time prior to a disaster for which data loss is acceptable, and a recovery time objective (RTO)—the maximum length of time allowed before services are restored.

RPO 管理数据备份和恢复策略,因为必须足够频繁地备份数据才能实现 RPO。当然,如果没有在其上运行的应用程序及其所在的环境和基础设施,数据就没有用,因此您需要能够重新部署正确版本的应用程序及其环境和基础设施。反过来,这意味着所有这些东西都必须仔细管理它们的配置,以便运营团队可以重现它们。

The RPO governs the data backup and restore strategy, since data must be backed up frequently enough that the RPO can be achieved. Of course data is no good without the applications operating on it and the environments and infrastructure it lives in, so you need to be able to redeploy the correct versions of the applications and their environments and infrastructure. This, in turn, means that all of these things must have their configuration carefully managed so they can be reproduced by the operations team.

为了满足业务所需的 RTO,可能有必要在第二个位置建立生产环境和基础设施的副本,以便在主系统出现故障时使用。应用程序应该能够处理这种可能性。对于高可用性应用程序,这意味着在应用程序运行时复制数据和配置。

In order to meet the business’ desired RTO, it might be necessary to establish a copy of the production environments and infrastructure in a second location that can be used if the primary systems fail. Applications should be able to deal with this eventuality. For high-availability applications, this means replicating data and configuration while the application is live.

一个相关的要求是归档:生产中的应用程序生成的数据量可能会很快变得非常大。应该有一些简单的方法来归档生产数据,以便在不填满磁盘或减慢应用程序的情况下将其保留用于审计或支持目的。

A related requirement is for archiving: The amount of data generated by an application in production may become very large very quickly. There should be some simple method of archiving production data so it can be kept for auditing or support purposes while not filling up the disk or slowing down the application.

作为业务连续性测试的一部分,您应该测试执行应用程序数据的备份、恢复和归档,以及检索和部署任何给定版本的应用程序,并向运营团队提供执行这些活动的流程作为发布计划的一部分。

You should have tested performing backups, recovery, and archiving of your application’s data as part of business continuity testing, as well as retrieving and deploying of any given version of your application, and provided the operations team with the process for performing each of these activities as part of your release plan.

使用运营团队熟悉的技术

Use the Technology the Operations Team Is Familiar With

运营经理希望使用他们的团队熟悉的技术来改变他们的环境,这样他们就可以拥有和维护他们的环境。

Operations managers want changes to be made to their environments using technology that is familiar to their team, so they can own and maintain their environments.

运维团队精通 Bash 或 PowerShell 是很常见的,但他们不太可能成为 Java 或 C# 忍者。但是,几乎可以肯定的是,他们会想要查看对其环境和基础架构的配置所做的更改。如果运营团队因为使用的技术和语言而无法理解部署过程,他们就不会熟悉,不可避免地会增加进行更改的风险。运营团队可能会否决他们不具备维护技能的部署系统。

It is quite common for operations teams to be well versed in Bash or PowerShell, but less likely that they will be Java or C# ninjas. However, it is almost certain that they will want to review changes being made to the configuration of their environments and infrastructure. If the operations team cannot understand the deployment process because it uses technologies and languages they are not familiar with, there is inevitably an increased risk of making changes. Operations teams may veto deployment systems they don’t have the skills to maintain.

开发团队和运营团队应该在每个项目开始时坐下来决定如何部署应用程序。运营团队或软件开发团队可能有必要学习一种商定的技术——可能是一种脚本语言,如 Perl、Ruby 或 Python,或者一种打包技术,如 Debian 打包系统或 WiX。

The development team and operations team should sit down at the beginning of every project and decide how deployment of the application will be performed. It may be necessary for either the operations team or the software development team to learn an agreed-upon technology—perhaps a scripting language such as Perl, Ruby, or Python, or a packaging technology such as the Debian packaging system or WiX.

两个团队都了解部署系统很重要,因为必须使用相同的过程将更改部署到每个环境——开发、持续集成、测试和生产——并且开发人员最初将负责创建它们。在某些时候,它们将被移交给负责维护它们的运营团队,这意味着他们应该从一开始就参与编写它们。用于部署和对环境和基础设施进行其他更改的技术应构成发布计划的一部分。

It is important that both teams understand the deployment system, because the same process must be used to deploy changes to every environment—development, continuous integration, testing, and production—and the developers will initially be responsible for creating them. At some point, they will be handed over to the operations team which will be responsible for maintaining them, which means that they should be involved from the start in writing them. The technologies to be used for deploying and making other changes to environments and infrastructure should form part of the release plan.

部署系统构成了应用程序的一个组成部分——它应该像应用程序的其余部分一样被测试和重构,并保持在版本控制中。如果不是这种情况(我们已经看到许多不是这种情况的项目),结果总是一组测试不佳、脆弱且难以理解的脚本,使变更管理充满风险和痛苦。

The deployment system forms an integral part of the application—it should be tested and refactored with the same care and attention as the rest of the application, and kept in version control. When this is not the case (and we have seen many projects where it is not), the result is always a set of poorly tested, brittle, and badly understood scripts that make change management risky and painful.

建模和管理基础设施

Modeling and Managing Infrastructure

除了利益相关者管理之外,本章中的所有其他内容都可以广泛地视为配置管理的一个分支。然而,对您的测试和生产环境实施完整的配置管理并非易事,这解释了我们在该主题上投入的篇幅。即便如此,我们也只会涵盖环境和基础设施管理的高级原则。

With the exception of stakeholder management, everything else in this chapter can be broadly considered a branch of configuration management. However, implementing full configuration management of your testing and production environments is nontrivial, which explains the amount of space we devote to this topic. Even so, we will only be covering the high-level principles of environment and infrastructure management.

图 11.1 服务器类型及其配置

Figure 11.1 Types of servers and their configuration

图片

在任何环境中都有许多不同类别的配置信息在起作用,所有这些都应该以自动化的方式进行供应和管理。图 11.1显示了一些服务器类型的示例,按抽象级别划分。

There are many different classes of configuration information at play in any environment, all of which should be provisioned and managed in an automated fashion. Figure 11.1 shows some examples of types of servers, divided up by level of abstraction.

如果您可以完全控制您正在创建的系统的技术选择,那么作为采购和启动过程的一部分,您应该询问自动化部署和配置硬件和软件基础设施本身的难易程度。拥有可以以自动化方式配置和部署的底层技术是自动化系统集成、测试和部署过程的必要条件。

If you have full control over the technology choices for the system you are creating, you should ask, as part of your procurement and inception process, how easy it will be to automate the deployment and configuration of the hardware and software infrastructure itself. Having underlying technology that can be configured and deployed in an automated fashion is a necessary condition for automating the processes of integration, testing, and deployment of your system.

即使您无法控制基础设施的选择,如果您打算完全自动化您的构建、集成、测试和部署,您也必须解决以下每个问题:

Even if you don’t have control over the selection of your infrastructure, if you intend to fully automate your build, integration, testing, and deployment, you must address each of the following questions:

• 我们将如何配置我们的基础设施?

• How will we provision our infrastructure?

• 我们将如何部署和配置构成我们基础设施一部分的各种软件?

• How will we deploy and configure the various bits of software that form part of our infrastructure?

• 一旦供应和配置好我们的基础设施,我们如何管理它?

• How do we manage our infrastructure once it is provisioned and configured?

现代操作系统有数千种不同的安装方式:不同的设备驱动程序、不同的系统配置设置以及大量会影响软件运行方式的参数。一些软件系统比其他软件系统更能容忍这个级别的差异。大多数现成的商业软件 (COTS) 预计会在各种软件和硬件配置中运行,因此不应太在意这一级别的差异——尽管您应该始终检查 COTS 的系统要求,因为采购或升级过程的一部分。然而,一个非常高性能的 Web 应用程序可能对微小的变化很敏感,例如数据包大小或文件系统配置的变化。

A modern operating system has thousands of ways in which one installation may differ from another: different device drivers, different system configuration settings, and a vast array of parameters that will influence the way in which your software will run. Some software systems are much more tolerant than others to differences at this level. Most commercial off-the-shelf software (COTS) is expected to run in a wide variety of software and hardware configurations, and so should not care too much about differences at this level—although you should always check the system requirements of your COTS as part of the procurement or upgrade process. However, a very high-performance web application may be sensitive to even tiny changes, such as variations in packet sizes or filesystem configuration.

对于大多数运行在服务器上的多用户应用程序来说,简单地接受操作系统和中间件的默认设置是不合适的。操作系统需要配置访问控制、防火墙和其他强化措施(例如禁用非必要服务)。需要配置数据库并为用户设置正确的权限,应用服务器需要部署组件,消息代理需要定义消息和注册订阅,等等。

For most multiuser applications that run on servers, it is not appropriate to simply accept the default settings of operating systems and middleware. Operating systems will need to have access control, firewalls, and other hardening measures (such as disabling nonessential services) configured. Databases will need to be configured and have users set up with the correct permissions, application servers will need to have components deployed, message brokers will need to have messages defined and subscriptions registered, and so on.

与交付过程的每个其他方面一样,您应该将创建和维护基础架构所需的一切都置于版本控制之下。至少,这意味着

As with every other aspect of your delivery process, you should keep everything you need to create and maintain your infrastructure under version control. At the least, that means

• 操作系统安装定义(例如 Debian Preseed、RedHat Kickstart 和 Solaris Jumpstart 使用的定义)

• Operating system install definitions (such as those used by Debian Preseed, RedHat Kickstart, and Solaris Jumpstart)

• 配置数据中心自动化工具,如 Puppet 或 CfEngine

• Configuration for data center automation tools like Puppet or CfEngine

• 一般基础设施配置,例如 DNS 区域文件、DHCP 和 SMTP 服务器配置文件、防火墙配置文件等

• General infrastructure configuration, such as DNS zone files, DHCP and SMTP server configuration files, firewall configuration files, and so forth

• 您用于管理基础架构的任何脚本

• Any scripts you use for managing your infrastructure

版本控制中的这些文件以与源代码相同的方式形成对部署管道的输入。在基础设施发生变化的情况下,部署管道的工作有三重。首先,在将所有应用程序推送到生产环境之前,它应该验证所有应用程序是否可以使用任何基础架构更改,确保每个受影响的应用程序的功能和非功能测试都通过新版本的基础架构。其次,它应该用于将更改推送到操作管理的测试和生产环境。最后,管道应该执行部署测试以确保新的基础设施配置已成功部署。

These files in version control form inputs to the deployment pipeline the same way the source code does. The job of the deployment pipeline in the case of infrastructural changes is threefold. First, it should verify that all applications will work with any infrastructural changes before they get pushed out to production environments, ensuring that every affected application’s functional and nonfunctional tests pass against the new version of the infrastructure. Second, it should be used to push changes out to operations-managed testing and production environments. Finally, the pipeline should perform deployment tests to ensure that the new infrastructure configuration has been deployed successfully.

回顾图 11.1,值得注意的是,用于部署和配置应用程序、服务和组件的脚本和工具通常与用于供应和管理其余基础设施的脚本和工具不同。有时,为部署应用程序而创建的进程也执行部署和配置中间件的任务。这些部署过程通常由负责相关应用程序的开发团队创建,但它们当然隐含地依赖于其他基础设施的就位和正确状态。

Referring back to Figure 11.1, it is worth observing that the scripts and tools used to deploy and configure applications, services, and components are often distinct from those used to provision and manage the rest of the infrastructure. Sometimes, the process created for deploying applications also performs the task of deploying and configuring middleware as well. These deployment processes are generally created by the development teams responsible for the application in question, but they of course have an implicit dependency on the rest of the infrastructure being in place and in the correct state.

处理基础设施时的一个重要考虑因素是它的共享程度。如果特定的基础结构配置仅与特定应用程序相关,那么它应该是应用程序部署管道的一部分,并且没有自己单独的生命周期。但是,如果某些基础架构在应用程序之间共享,那么您将面临管理应用程序与它们所依赖的基础架构版本之间的依赖关系的问题。这意味着记录每个版本的应用程序需要哪个版本的基础设施才能工作。然后,您需要设置一个单独的管道来推出基础架构更改,确保影响多个应用程序的更改以遵守依赖关系规则的方式通过交付过程。

An important consideration when dealing with infrastructure is the extent to which it is shared. If a particular piece of infrastructural configuration is relevant only to a particular application, then it should be part of the deployment pipeline of the application and have no separate lifecycle of its own. However, if some infrastructure is shared between applications, then you are faced with a problem of managing dependencies between applications and the versions of infrastructure they depend on. That means recording which version of the infrastructure each version of the application requires in order to work. You then need to set up a separate pipeline to push out infrastructural changes, ensuring that changes affecting multiple applications move through the delivery process in a way that obeys the dependency rules.

控制对基础设施的访问

Controlling Access to Your Infrastructure

如果您的组织规模较小或较新,您可以为所有基础设施的配置管理设计一个策略。如果您的现有系统没有得到很好的控制,您将需要弄清楚如何控制它。这包括三个部分:

If your organization is small or new, you have the luxury of devising a strategy for the configuration management of all of your infrastructure. If you have an existing system that is not under good control, you’ll need to work out how to get it under control. There are three parts to this:

• 控制访问以防止任何人在未经批准的情况下进行更改

• Controlling access to prevent anyone from making a change without approval

• 定义用于更改基础架构的自动化流程

• Defining an automated process for making changes to your infrastructure

• 监控您的基础架构以在问题发生时立即检测到它们

• Monitoring your infrastructure to detect any issues as soon as they occur

虽然一般来说我们不喜欢锁定事情并建立审批流程,但当涉及到您的生产基础设施时,这是必不可少的。作为其必然结果,由于我们认为您应该像对待生产环境一样对待您的测试环境,所以同样的过程应该适用于两者。

While in general we are not fans of locking things down and establishing approval processes, when it comes to your production infrastructure it is essential. As a corollary of that, since we believe that you should treat your testing environments the same way you treat your production environments, the same process should apply to both.

必须锁定生产环境以防止未经授权的访问,不仅来自组织外部的人员,而且来自组织内部的人员——甚至是操作人员。否则,当出现问题时,很容易登录到有问题的环境并四处寻找解决问题的方法(这个过程有时被礼貌地称为问题解决启发式). 由于两个原因,这几乎总是一个糟糕的想法。首先,它通常会导致服务中断(人们倾向于尝试重新启动或随机应用服务包)。其次,如果以后出现问题,没有记录谁在什么时候做了什么,这意味着你不可能找出你遇到的任何问题的原因。在这种情况下,您也可以从头开始重新创建环境,使其处于已知状态。

It is essential to lock down the production environments to prevent unauthorized access not only from people outside your organization, but also from people within it—even operations staff. Otherwise it is just too tempting, when something goes wrong, to log into the environment in question and poke around to resolve problems (a process sometimes politely called a problem-solving heuristic). This is almost always a terrible idea for two reasons. First, it usually leads to service disruptions (people tend to try rebooting or applying service packs at random). Second, if something goes wrong later, there is no record of who did what when, which means it’s impossible to work out the cause of whatever problem you’re facing. In this situation, you may as well re-create the environment from scratch so it is in a known state.

如果您的基础架构无法通过自动化流程从头开始重新创建,那么首先要做的就是实施访问控制,以便在未经批准的情况下无法对任何基础架构进行更改。Visible Ops Handbook称之为“稳定患者”。这无疑会引起很多烦恼,但这是下一步的先决条件:创建用于管理基础架构的自动化流程。如果不关闭访问权限,操作人员最终会将所有时间都花在救火上,因为计划外的更改总是会破坏事情。设置何时完成工作和强制执行访问控制的一个好方法是创建维护窗口。

If your infrastructure is not capable of being re-created from scratch via an automated process, the first thing to do is implement access control so that changes cannot be made to any infrastructure without going through an approval process. The Visible Ops Handbook calls this “stabilizing the patient.” This will undoubtedly cause much annoyance, but it is a prerequisite for the next step: creating an automated process for managing infrastructure. Without turning off access, operations staff end up spending all of their time firefighting because unplanned changes break things all the time. A good way to set the expectations of when work will be done and enforce access control is to create maintenance windows.

对生产和测试环境进行更改的请求应通过更改管理流程。这不必是官僚作风:正如The Visible Ops Handbook中指出的那样,许多在 MTBF(平均无故障时间)和 MTTR(平均修复时间)方面表现最佳的组织“每周​​进行 1000-1500 次更改,变更成功率超过 99%。”

Requests to make changes to your production and testing environments should go through a change management process. This need not be bureaucratic: As is pointed out in The Visible Ops Handbook, many organizations which perform best in terms of the MTBF (mean time between failures) and MTTR (mean time to repair) “were doing 1000–1500 changes per week, with a change success rate of over 99%.”

然而,测试环境变更的批准当然比变更生产的批准更容易获得。通常,对生产环境的更改必须得到部门负责人或您的 CTO 的批准(取决于您组织的规模及其监管环境)。然而,如果要求批准对 UAT 环境的更改,大多数 CTO 会感到不安。重要的一点是,您将在测试环境中经历与在生产环境中相同的过程。

However, the approval for changes to your testing environments should of course be easier to get than the approval to change production. Often, changes to production environments have to be approved by heads of departments or your CTO (depending on the size of your organization and its regulatory environment). Most CTOs, however, would be upset if asked to approve changes to the UAT environment. The important point is that you are going through the same process for your testing environments as you do for production.

改变基础设施

Making Changes to Infrastructure

当然,有时有必要对基础架构进行更改。有效的变革管理流程有几个基本特征。

Of course sometimes it is necessary to make changes to infrastructure. There are several essential characteristics of an effective change management process.

• 每次更改,无论是更新防火墙规则还是部署新版本的旗舰服务,都应该经过相同的更改管理流程。

• Every change, whether it’s updating firewall rules or deploying a new version of your flagship service, should go through the same change management process.

• 这个过程应该使用一个单一的票务系统来管理,每个人都可以登录并生成有用的指标,例如每次更改的平均周期时间。

• This process should be managed using a single ticketing system that everybody can log into and which generates useful metrics such as average cycle time per change.

• 应记录所做的确切更改,以便以后轻松审核。

• The exact change that is made should be logged so it can be easily audited later.

• 应该可以查看对每个环境所做更改的历史记录,包括部署。

• It should be possible to see a history of changes made to every environment, including deployments.

• 您要进行的更改应该首先在您的一个类似生产的测试环境中进行测试,并且应该运行自动化测试以确保它不会破坏使用该环境的任何应用程序。

• The change you want to make should first have been tested on one of your production-like testing environments, and automated tests should be run to ensure that it doesn’t break any of the applications that use the environment.

• 应该对版本控制进行更改,然后通过用于部署基础结构更改的自动化流程应用。

• The change should be made to version control and then applied through your automated process for deploying infrastructural changes.

• 应该有一个测试来验证更改是否有效。

• There should be a test to verify that the change has worked.

创建用于部署来自版本控制的基础设施变更的自动化流程是良好变更管理的核心。最有效的方法是要求通过中央系统对您的环境进行所有更改。使用测试环境来计算您想要进行的更改,在一个全新的、类似生产的暂存环境中对其进行测试,将其放入配置管理中,以便将来的重建将其纳入、获得批准,然后推出自动化系统改变。许多组织已经为这个问题建立了自己的解决方案,但如果您没有,您可以使用数据中心自动化工具,如 Puppet、CfEngine、BladeLogic、Tivoli 或 HP Operations Center。

Creating an automated process for deploying infrastructural changes from version control is at the core of good change management. The most effective way to do this is to require all changes to be made to your environments via a central system. Use a testing environment to work out the change you want to make, test it in a fresh, production-like staging environment, put it into configuration management so that future rebuilds incorporate it, have it approved, and then have the automated system roll out the change. Many organizations have built their own solutions to this problem, but if you do not have one, you can use a data center automation tool like Puppet, CfEngine, BladeLogic, Tivoli, or HP Operations Center.

实施可审计性的最佳方法是让所有更改都由自动化脚本进行,以后可以引用这些更改,以防有人需要确切地找出所做的事情。通常,出于这个原因,我们更喜欢自动化而不是文档。书面文档永远不能保证记录的更改已正确执行,并且某人声称他们所做的与他们实际所做的之间的差异足以导致可能需要数小时或数天才能追踪到的问题。

The best way to enforce auditability is to have all changes made by automated scripts which can be referenced later in case anybody needs to find out exactly what was done. In general, we prefer automation over documentation for this reason. Written documentation is never a guarantee that the documented change was performed correctly, and the differences between what somebody claims they did and what they actually did are sufficient to cause a problem that may take hours or days to track down.

管理服务器供应和配置

Managing Server Provisioning and Configuration

供应服务器和管理它们的配置在小型甚至中型操作中经常被忽视,原因很简单,因为它看起来很复杂。几乎每个人启动和运行服务器的最初经验都来自获取安装介质,将其放入计算机,然后进行交互式安装,然后是不受控制的配置管理过程。然而,这很快就会导致服务器成为“艺术品”,从而导致服务器和系统之间的行为不一致,一旦发生故障就无法轻易重建。此外,配置新服务器是一个手动、重复、资源密集型且容易出错的过程——恰恰是可以通过自动化解决的问题。

Provisioning servers and managing their configuration is often overlooked in small and even medium-sized operations for the simple reason that it seems complicated. Almost everybody’s initial experience of getting a server up and running comes from taking the install media, putting it into the computer, and doing an interactive install, followed by an uncontrolled configuration management process. However, this quickly leads to servers that are “works of art,” which leads to inconsistent behavior between servers and systems that cannot be re-created easily in the event of a failure. Furthermore, provisioning new servers is a manual, repetitive, resource-intensive, and error-prone process—exactly the kind of problem that can be solved with automation.

图 11.2 服务器的自动供应和配置

Figure 11.2 Automated provisioning and configuration of servers

图片

在高层次上,配置服务器——无论是用于测试还是生产环境——首先是在数据中心放置一个新盒子并将其连接起来。一旦完成,它生命周期的几乎每个部分,包括第一次启动它,可以以全自动方式远程完成。您可以使用 IPMI 或 LOM 等带外管理系统打开盒子并让它进行网络引导,以通过 PXE(如下所述)安装基本操作系统,其中应包括数据中心管理工具的代理. 您的数据中心管理工具(下图中的 Puppet)然后从那时起管理盒子的配置。这个完全自动化的过程如图 11.2所示。

At a high level, provisioning servers—whether for testing or production environments—starts with putting a new box in your data center and wiring it in. Once that’s done, pretty much every part of its lifecycle, including powering it up for the first time, can be done remotely in a fully automated fashion. You can use out-of-band management systems such as IPMI or LOM to turn on the box and have it network-boot to install a base operating system via PXE (described below), which should include an agent for your data center management tool. Your data center management tool (Puppet in the diagram below) then manages the configuration of the box from then onwards. This fully automated process is shown in Figure 11.2.

配置服务器

Provisioning Servers

有几种创建操作系统基线的方法:

There are several ways to create operating system baselines:

• 完全手动的过程

• A fully manual process

• 自动远程安装

• Automated remote installation

• 虚拟化

• Virtualization

我们不会考虑完全手动的过程,只是要注意它不能可靠地重复,因此不能扩展。然而,这就是开发团队通常管理其环境的方式。通常情况下,开发人员工作站甚至由开发团队管理的持续集成环境都是经过长时间积累的艺术品。这些环境与您的应用程序实际所处的环境无关。这本身就是效率低下的一个巨大来源。实际上,这些系统的管理方式应与您管理测试和生产环境的方式相同。

We won’t consider the fully manual process, except to note that it is not reliably repeatable and therefore doesn’t scale. However, this is how development teams often manage their environments. It is often the case that developer workstations and even continuous integration environments managed by development teams are works of art that have accumulated cruft over long periods of time. These environments bear no relation to the environment your application will actually live in. This in itself can be a huge source of inefficiency. Really, these systems should be managed the same way you manage testing and production environments.

稍后将在第 303 页的“虚拟化”部分中考虑将虚拟化作为创建操作系统基线和管理环境的一种方式

Virtualization as a way of creating operating system baselines and managing environments will be considered later, in the “Virtualization” section on page 303.

自动远程安装是使用新物理机并启动并运行它的最佳选择(即使您计划稍后将其用作虚拟主机)。最好的起点是 PXE(Preboot eXecution Environment)或 Windows 部署服务。

Automated remote installation is the best option to take a new physical machine and get it up and running (even if you plan to later use it as a virtual host). The best place to start with this is PXE (Preboot eXecution Environment) or Windows Deployment Services.

PXE 是通过以太网启动机器的标准。当您在 BIOS 中选择通过网络启动时,底层发生的是 PXE。该协议使用修改后的 DHCP 版本来查找提供引导映像的服务器。当用户选择要从中启动的映像时,客户端然后通过 TFTP 将适当的映像加载到 RAM 中。标准的 Internet Services Consortium DHCP 服务器 dhcpd 随所有 Linux 发行版一起提供,可以配置为提供 PXE 服务,然后您需要配置 TFTP 服务器以提供实际图像。如果您使用的是 RedHat,一个名为 Cobbler 的应用程序将通过 PXE 提供一系列 Linux 操作系统映像。它还可以让您(如果您运行的是 RedHat 机器)使用您选择的操作系统映像启动新的虚拟机。还有一个提供 PXE 服务的 Hudson 插件。BMC 的 BladeLogic 包括一个 PXE 服务器。

PXE is a standard for booting boxes over Ethernet. When you choose to boot via the network in your BIOS, what happens under the hood is PXE. The protocol uses a modified version of DHCP to find servers that offer images to boot from. When the user has selected the image to boot from, the client then loads the appropriate image into RAM via TFTP. The standard Internet Services Consortium DHCP server, dhcpd, which ships with all Linux distributions, can be configured to provide PXE services, and you’ll then need to configure a TFTP server to provide the actual images. If you’re using RedHat, an application called Cobbler will serve a selection of Linux operating system images via PXE. It will also let you (if you are running a RedHat box) spin up new virtual machines with your chosen OS image. There is also a plugin for Hudson which provides PXE services. BMC’s BladeLogic includes a PXE server.

几乎所有常见的 UNIX 风格都提供适合 PXE 的映像。当然,您也可以创建自己的自定义映像——RedHat 和 Debian 包管理系统都允许您将已安装系统的状态保存在一个文件中,该文件随后可用于初始化其他系统。

Pretty much every common UNIX flavor provides images suitable for PXE. Of course you can also create your own custom images—both the RedHat and Debian package management systems allow you to save the state of an installed system in a file which can then be used to initialize other systems.

一旦您配置了基本系统,您将需要对其进行配置。一种方法是使用操作系统的无人值守安装过程:RedHat 的 Kickstart、Debian 的 Preseed 或 Solaris 的 Jumpstart。这些可用于执行安装后活动,例如安装操作系统补丁和决定运行哪些守护进程。安装后的下一步是为您的基础设施管理系统在盒子上安装一个代理,并让这些工具从那时起管理您的操作系统配置。

Once you’ve got your base system provisioned, you’ll want to configure it. One way to do this is to use your operating system’s unattended install process: RedHat’s Kickstart, Debian’s Preseed, or Solaris’ Jumpstart. These can be used to perform postinstall activities such as installing operating system patches and deciding which daemons to run. The next step after installation is to get an agent for your infrastructure management system installed on the box, and have those tools manage your operating system’s configuration from then on.

PXE 的 Windows 模拟被称为 Windows 部署服务——实际上,它在底层使用 PXE。WDS 随 Windows Server 2008 Enterprise Edition 提供,也可以安装在 Windows Server 2003 上。它可用于从 Windows 2000 开始启动 Windows 版本(不包括ME)—尽管从 Vista 开始,事情已经大大简化了。为了使用 WDS,您需要一个 ActiveDirectory 域、一个 DHCP 服务器和一个 DNS 服务器。然后您可以安装(如果需要)并启用 WDS。要设置配置文件以在 WDS 中启动,您需要两件事:启动映像和安装映像。引导映像是通过 PXE 加载到 RAM 中的内容——在 Windows 的情况下,这是一种称为 WinPE(Windows 预安装环境)的软件,它是您在引导 Vista(或更高版本)安装 DVD 时运行的。安装映像是引导映像加载到您机器上的实际完整安装映像。从 Vista 开始,这两个映像都在安装 DVD 的 Sources 目录中以 BOOT.WIM 和 INSTALL.WIM 的形式提供。鉴于这两个文件,

The Windows analog of PXE is known as Windows Deployment Services—and indeed, it uses PXE under the hood. WDS comes on Windows Server 2008 Enterprise Edition, and can also be installed on Windows Server 2003. It can be used to boot versions of Windows from Windows 2000 onwards (not including ME)—although things have been streamlined considerably from Vista forwards. In order to use WDS, you’ll need an ActiveDirectory domain, a DHCP server, and a DNS server. You can then install (if required) and enable WDS. To set up a profile to boot off in WDS, you need two things: a boot image and an install image. The boot image is what is loaded into RAM by PXE—in the case of Windows, this is a bit of software called WinPE (Windows Preinstallation Environment), which is what you run when you boot a Vista (or later) installation DVD. The install image is the actual full install image which the boot image loads onto your machine. From Vista onwards, both of these images are available in the Sources directory in the installation DVD as BOOT.WIM and INSTALL.WIM. Given these two files, WDS will do all the configuration necessary to make them available over the network for booting.

您还可以为 WDS 创建自己的自定义安装映像。正如 Ben Armstrong [9EQDL4] 所述,使用 Microsoft Hyper-V 最容易做到这一点。只需根据要从中创建映像的操作系统启动虚拟机即可。按照您想要的方式对其进行配置,在其上运行 Sysprep,然后使用 ImageX 将驱动器映像转换为可以向 WDS 注册的 WIM 文件。

You can also create your own custom install images for WDS. This is most easily done using Microsoft Hyper-V, as described by Ben Armstrong [9EQDL4]. Simply start a virtual machine based off the operating system you want to create an image from. Configure it the way you want it, run Sysprep on it, and then use ImageX to turn the drive image into a WIM file that you can register with WDS.

服务器的持续管理

Ongoing Management of Servers

安装操作系统后,您需要确保其配置不会以不受控制的方式更改。这意味着首先要确保除了运营团队之外,没有人能够登录这些框,其次,任何更改都是使用自动化系统执行的。这包括应用操作系统服务包、升级、安装新软件、更改设置或执行部署。

Once you have got the operating system installed, you will need to ensure that its configuration doesn’t change in an uncontrolled manner. That means ensuring, first, that nobody is able to log into the boxes except the operations team, and second, that any changes are performed using an automated system. That includes applying OS service packs, upgrades, installing new software, changing settings, or performing deployments.

配置管理过程的目标是确保配置管理是声明式和幂等的——这意味着您配置基础架构的所需状态,并且系统确保应用此配置,以便无论基础架构的初始状态如何,最终结果是相同的,即使重新应用相同的配置也是如此。这在 Windows 和 UNIX 世界中都是可能的。

The goal of your configuration management process is to ensure that configuration management is declarative and idempotent—which means you configure the desired state of your infrastructure and a system ensures that this configuration is applied so that, whatever the initial state of the infrastructure, the end result is the same, even if the same configuration is reapplied. This is possible in both the Windows and UNIX worlds.

一旦这个系统就位,就可以从一个中央版本化配置管理系统管理基础架构中的所有测试和生产环境。然后,您可以获得以下好处:

Once this system is in place, it becomes possible to manage all the testing and production environments within your infrastructure from a central, versioned configuration management system. You can then reap the following benefits:

• 您可以确保跨所有环境的一致性。

• You can ensure consistency across all environments.

• 您可以轻松供应与现有环境的配置相匹配的新环境,例如创建与生产相匹配的暂存环境。

• You can easily provision new environments that match the configuration of existing ones, for example to create staging environments that match production.

• 如果您的其中一个盒子出现硬件故障,您可以放入一个新盒子并使用完全自动化的过程按照与旧盒子相同的方式对其进行配置。

• If you have a hardware failure on one of your boxes, you can put in a new box and have it configured the same way as the old one using a fully automated process.

在 Windows 上,Microsoft 提供(除了 Windows 部署服务之外)用于管理 Microsoft 基础结构的解决方案:System Center Configuration Manager。SCCM 使用 ActiveDirectory 和 Windows 软件更新服务来管理操作系统配置,包括组织中每个机器的更新和设置。您还可以使用 SCCM 部署应用程序。SCCM 还与 Microsoft 虚拟化技术对话,使您可以像管理物理服务器一样管理虚拟服务器。访问控制使用组策略进行管理,组策略与 ActiveDirectory 集成并内置于自 Windows 2000 以来的所有 Microsoft 服务器中。

On Windows, Microsoft provides (in addition to Windows Deployment Services) a solution for managing your Microsoft infrastructure: System Center Configuration Manager. SCCM uses ActiveDirectory and Windows Software Update Services to manage operating system configuration, including updates and settings on each of the boxes in your organization. You can also deploy applications using SCCM. SCCM also talks to Microsoft virtualization technologies, allowing you to manage virtual servers the same way you manage physical ones. Access control is managed using Group Policy, which integrates with ActiveDirectory and is built into all Microsoft servers since Windows 2000.

回到 UNIX 世界,LDAP 与通常的 UNIX 访问控制一起用于控制谁可以在哪些机器上做什么。有许多解决方案可用于持续管理操作系统配置,包括安装哪些软件和更新。也许最流行的是 CfEngine、Puppet 和 Chef,但也存在其他一些类似的工具,例如 Bcfg2 和 LCFG [9bhX9H]。在撰写本文时,唯一支持 Windows 的工具是 WPKG,它不支持 UNIX 平台。但是,正在对 Puppet 和 Chef 进行工作以添加 Windows 支持。另外值得一提的是神奇的 Marionette Collective(简称 mcollective),这是一个使用消息总线来查询和管理大量服务器的工具。它有远程控制其他服务的插件,可以与 Puppet 和 Facter 对话。

Back in the UNIX world, LDAP along with the usual UNIX access controls are used to control who can do what on which boxes. There are a number of solutions for managing operating system configuration, including which software and updates are installed, on an ongoing basis. Perhaps the most popular are CfEngine, Puppet, and Chef, but several other similar tools exist, such as Bcfg2 and LCFG [9bhX9H]. At the time of writing, the only such tool which supports Windows is WPKG, which does not support UNIX platforms. However, work was being done on both Puppet and Chef to add Windows support. Also worth mentioning is the fantastic Marionette Collective (mcollective for short), a tool that uses a message bus to query and manage large numbers of servers. It has plugins to remotely control other services, and can talk to Puppet and Facter.

或者,如您所料,可以使用功能强大且昂贵的商业工具来管理您的服务器基础架构。除了 Microsoft 之外,主要参与者是 BMC 及其 BladeLogic 套件、IBM 及其 Tivoli 和 HP 及其 Operations Center 套件。

Alternatively, there are, as you might expect, powerful and expensive commercial tools to manage your server infrastructure. Apart from Microsoft, the main players are BMC, with their BladeLogic suite, IBM, with Tivoli, and HP, with their Operations Center suite.

所有这些工具——无论是开源的还是商业的——都以类似的方式运作。您指定您希望盒子的状态,该工具可确保您的基础设施处于指定状态。这是通过让代理在您的每个盒子上运行以获取配置并更改其他盒子的状态以匹配它,执行安装软件和进行配置更改等任务来完成的。这种系统的关键特征是它们强制执行幂等性——也就是说,无论当代理找到盒子时盒子处于什么状态,无论代理应用配置多少次,盒子总是会以期望的最终状态结束。简而言之,您只需指定所需的结束状态,启动工具,它会不断做出适当的调整。这实现了使您的基础架构自主的更高目标——换句话说,自我修复。

All of these tools—whether open source or commercial—operate in a similar way. You specify what you want the state of your boxes to be, and the tool ensures that your infrastructure is in the specified state. This is done by having agents run on each of your boxes to pick up the configuration and alter the state of the other boxes to match it, performing tasks such as installing software and making configuration changes. The key characteristic of such systems is that they enforce idempotence—that is, whatever state the box is in when the agent finds it, and however many times the agent applies the configuration, the box will always end up in the desired end state. In short, you can just specify the desired end state, fire up the tool, and it will continually make the appropriate adjustments. This achieves the higher goal of making your infrastructure autonomic—in other words, self-healing.


图片

您应该能够使用一组原始服务器并从头开始将所有内容部署到它们。事实上,将自动化或虚拟化引入您的构建、部署、测试和发布策略的一个好方法是使其成为您的环境供应过程的测试。一个值得提出和测试的好问题是:如果发生灾难性故障,需要多长时间才能提供我的生产环境的新副本?

You should be able to take a vanilla set of servers and deploy everything to them from scratch. Indeed, a great way to introduce automation or virtualization into your build, deploy, test, and release strategy is to make it a test of your environment provisioning process. A good question to ask, and to test, is: How long would it take to provision a new copy of my production environment if it failed catastrophically?


对于大多数开源工具,您的环境配置信息存储为一系列文本文件,可以保存在版本控制中。这反过来意味着您的基础设施的配置是自我记录的——您可以直接转到版本控制以查看其当前的预期状态。商业工具通常包括用于管理配置信息的数据库和用于编辑配置信息的点击式 UI。

In the case of most open source tools, your environments’ configuration information is stored as a series of text files that can be kept in version control. This in turn means your infrastructure’s configuration is self-documenting—you can just go to version control to see its current expected state. The commercial tools typically include databases to manage configuration information and clicky UIs for editing it.

我们将更详细地介绍 Puppet,因为它是目前最流行的开源系统之一(还有 CfEngine 和 Chef)。其他工具的基本原理相同。Puppet 通过针对配置信息量身定制的声明式、外部域特定语言 (DSL) 来管理配置。这允许复杂的企业范围配置,将通用模式提取到可以共享的模块中。因此,您可以避免重复配置信息。

We’ll go into a bit more detail on Puppet, because it is one of the most popular open source systems currently available (along with CfEngine and Chef). The underlying principles are the same for the other tools. Puppet manages configuration through a declarative, external domain-specific language (DSL) tailored to configuration information. This allows for complex enterprise-wide configurations with common patterns extracted into modules that can be shared. Thus you can avoid duplicating configuration information.

Puppet 配置由中央主服务器管理。该服务器运行 Puppet master 守护进程 (puppetmasterd),它有一个它控制的机器列表。每台受控机器都运行 Puppet 代理 (puppetd)。它与服务器通信以确保 Puppet 控制下的服务器与最新版本的配置同步。

Puppet configuration is managed by a central master server. This server runs the Puppet master daemon (puppetmasterd) which has a list of machines that it controls. Each of the controlled machines run the Puppet agent (puppetd). It communicates with the server to ensure that the servers under Puppet’s control are synchronized with the latest version of the configuration.

当配置更改时,Puppetmaster 会将更改传播到所有需要更新的客户端,安装和配置新软件,并在必要时重新启动服务器。配置是声明性的,描述了每个服务器的期望最终状态。这意味着它们可以从任何起始状态进行配置,包括 VM 的新副本或新配置的机器。

When a configuration changes, the Puppetmaster will propagate that change to all the clients that need to be updated, install and configure the new software, and restart the servers where necessary. The configuration is declarative, and describes the desired end state of each server. This means they can be configured from any starting state, including a fresh copy of a VM or a newly provisioned machine.

让我们以安装Postfix为例来说明如何使用Puppet。我们将编写一个模块来定义我们希望如何在我们的邮件服务器上配置 Postfix。模块由清单和可选的模板和其他文件组成。我们将创建一个名为postfix的新模块来保存我们的新清单,它定义了应该如何安装 Postfix。这意味着在模块根目录 (/etc/puppet/modules) 下创建一个名为 postfix/manifests 的目录,并在名为 init.pp 的文件中创建一个清单:

Let’s take installing Postfix as an example of how to use Puppet. We’re going to write a module defining how we want Postfix to be configured on our mail server. Modules consist of manifests and, optionally, templates and other files. We’re going to create a new module called postfix to hold our new manifest, which defines how Postfix should be installed. This means creating a directory called postfix/manifests under the modules root (/etc/puppet/modules), and creating a manifest in a file called init.pp there:

图片

该文件定义了一个描述如何安装 Postfix 的类。package语句确保安装了postfixPuppet 可以与所有流行的打包系统对话,包括 Yum、Aptitude、RPM、Dpkg、Sun 的包管理器、Ruby Gems 以及 BSD 和 Darwin 端口。服务语句确保 Postfix 服务已启用并正在运行文件语句在框上创建文件 /etc/postfix/main.cf,从 erb 模板中获取它。erb 模板是从 Puppetmaster 文件系统上的 /etc/puppet/modules/[module name]/templates 获取的,因此您需要在 /etc/puppet/modules/postfix/templates 中创建 main.cf.erb 文件。

This file defines a class which describes how to install Postfix. The package statement ensures that the postfix package is installed. Puppet can talk to all popular packaging systems, including Yum, Aptitude, RPM, Dpkg, Sun’s package manager, Ruby Gems, and BSD and Darwin ports. The service statement ensures that the Postfix service is enabled and running. The file statement creates the file /etc/postfix/main.cf on the box, taking it from an erb template. The erb template is fetched from /etc/puppet/modules/[module name]/templates on the Puppetmaster’s filesystem, so you’d create the main.cf.erb file in /etc/puppet/modules/postfix/templates.

在 Puppet 的主 site.pp 文件中定义了哪些清单适用于哪些主机:

Which manifests are to be applied to which hosts is defined in Puppet’s main site.pp file:

图片

在这个文件中,我们告诉 Puppet 将 Postfix 清单应用到主机 smtp.thoughtworks.com。还有一个默认节点的定义,它被应用于每个安装了 Puppet 的盒子。我们使用这个目标来确保所有框都设置为太平洋时区(此语法创建一个符号链接)。

In this file, we tell Puppet to apply the Postfix manifest to the host smtp.thoughtworks.com. There is also a definition for the default node, which gets applied to every box with Puppet installed on it. We’ve used this target to ensure that all boxes are set to the Pacific timezone (this syntax creates a symbolic link).

这是一个更高级的示例。在许多组织中,将应用程序打包并存储在组织包服务器上是有意义的。但是,您不希望必须配置每个服务器以手动查看组织的包服务器。在这个例子中,我们让 Puppet 告诉我们的盒子我们的自定义 Apt 存储库在哪里,将正确的 Apt GPG 密钥添加到这些盒子,并添加一个 crontab 条目以在每晚午夜运行 Apt 更新。

Here’s a more advanced example. In many organizations, it makes sense to have applications packaged up and stored on an organizational package server. However, you don’t want to have to configure each of your servers to look at the organization’s package server by hand. In this example, we have Puppet tell our boxes where our custom Apt repository is, add the correct Apt GPG key to these boxes, and add a crontab entry to run an Apt update every night at midnight.

图片

主要的apt类首先检查应用清单的节点是否正在运行 Debian。这是一个使用关于客户端的事实的示例——变量$operatingsystem是根据 Puppet 对客户端的了解自动预定义的几个变量之一。在命令行上运行facter以列出 Puppet 已知的所有事实。然后,我们将文件 custom-repository 从 Puppet 的内部文件服务器复制到盒子上的正确位置,并向每晚运行apt-get update的 root 的 crontab 添加一个条目。crontab 操作是幂等的——也就是说,如果条目已经存在,则不会重新创建它。apt::key定义从 Puppet的文件服务器复制 GPG 密钥,并运行apt-key add命令就可以了。如果 Apt 已经知道密钥(这是unless行),我们通过告诉命令不要运行来确保幂等性。

The main apt class, first of all, checks that the node the manifest is being applied to is running Debian. This is an example of using a fact about the client—the variable $operatingsystem is one of several that are automatically predefined based on what Puppet knows about the client. Run facter on the command line to list all the facts known by Puppet. We then copy the file custom-repository from Puppet’s internal file server to the right place on the box, and add an entry to root’s crontab which runs apt-get update every night. The crontab action is idempotent—that is, the entry won’t be re-created if it already exists. The apt::key definition copies the GPG key from Puppet’s fileserver, and runs the apt-key add command on it. We ensure idempotence by telling the command not to run if Apt already knows about the key (this is the unless line).

您需要确保文件 custom-repository,定义自定义 Apt 存储库,和 custom-repository-gpgkey,包含它的 GPG 密钥,放置在 /etc/puppet/modules/apt/ 目录中的 Puppet master 服务器上文件。然后,包括如下定义,代入正确的密钥 ID:

You need to ensure that the files custom-repository, defining the custom Apt repositories, and custom-repository-gpgkey, containing the GPG key for it, are placed on the Puppet master server in the directory /etc/puppet/modules/apt/files. Then, include the definitions as follows, substituting in the correct key ID:

图片

请注意,Puppet 旨在与版本控制一起工作:/etc/puppet 下的所有内容都应保持在版本控制之下,并且只能通过版本控制进行更改。

Note that Puppet is designed to work with version control: everything under /etc/puppet should be kept under version control and changed only through version control.

管理中间件的配置

Managing the Configuration of Middleware

一旦您的操作系统配置得到妥善管理,您就需要考虑位于它之上的中间件的管理。中间件——无论是 Web 服务器、消息系统还是商业现成软件 (COTS)——都可以分解为三个部分:二进制文件、配置、和数据。这三者具有不同的生命周期,因此独立对待它们很重要。

Once your operating system’s configuration is properly managed, you need to think about the management of the middleware that sits on top of it. Middleware—whether web servers, messaging systems, or commercial off-the-shelf software (COTS)—can be decomposed into three parts: binaries, configuration, and data. The three have different lifecycles which makes it important to treat them independently.

管理配置

Managing Configuration

数据库模式、Web 服务器配置文件、应用程序服务器配置信息、消息队列配置,以及系统工作所需更改的所有其他方面都应该在版本控制下。

Database schemas, web server configuration files, application server configuration information, message queue configuration, and every other aspect of the system that needs to be changed for your system to work should be under version control.

对于大多数系统,操作系统和中间件之间的区别相当模糊。例如,如果您使用的是基于 Linux 构建的开源堆栈,几乎所有中间件都可以使用与操作系统相同的方式进行管理,使用 Puppet 或其他类似工具之一。在这种情况下,您不必做任何特殊的事情来管理您的中间件。对于其余的中间件,只需遵循与上一节中 Postfix 示例中相同的模型:告诉 Puppet 确保安装正确的包,并从签入版本控制的 Puppet 主服务器上的模板更新配置。添加新网站和新组件等操作也可以通过这种方式进行管理。在 Microsoft 世界中,您可以使用 System Center Configuration Manager,

For most systems, the distinction between the operating system and the middleware is a fairly hazy one. If you’re using an open source stack built on Linux, for example, pretty much all the middleware can be managed in the same way as the operating system, using Puppet or one of the other similar tools. In this case, you don’t have to do anything special to manage your middleware. Simply follow the same model as in the Postfix example in the previous section for the rest of your middleware: tell Puppet to ensure the right packages are installed, and update the configuration from templates on the Puppet master server checked into version control. Operations like adding new websites and new components can be managed in this way too. In the Microsoft world, you can use System Center Configuration Manager, or one of the commercial tools like BladeLogic or Operations Center.

如果您的中间件不是标准操作系统安装的一部分,下一个最好的办法是使用操作系统的包管理系统将其打包并将其放在组织的内部包服务器上。然后你可以使用你选择的服务器管理系统来管理这个使用相同模型的中间件。

If your middleware isn’t part of the standard operating system install, the next best thing is to package it up using your operating system’s package management system and put it on your organization’s internal package server. Then you can use your chosen server management system to manage this middleware using the same model.

然而,有一些中间件不受这种处理的影响——通常是那些在设计时没有考虑脚本和静默安装的中间件。我们将在下一节中处理这种情况。

However, there are some bits of middleware that are not susceptible to this treatment—usually those which are not designed with scripting and silent installation in mind. We will tackle this scenario in the following section.

我们在前面的侧边栏中描述的项目是几年前完成的。如果我们现在开始,我们会在一开始就更加小心地管理与各种测试和生产环境相关的配置信息。我们也会在项目的早期进行必要的工作,尽可能的减少这个过程中的人工步骤,为大家节省很多工作量。

The project we describe in the preceding sidebar was completed a few years ago. If we were starting it now, we would be much more careful at the outset to manage the configuration information associated with the various test and production environments. We would also carry out the necessary work early in the project to eliminate manual steps in this process as far as possible and save everyone a lot of work.

与中间件相关的配置信息与用您最喜欢的编程语言编写的程序一样,都是系统的一部分。许多现代中间件都支持可编写脚本的配置方法:XML 配置很常见,有些还提供适合编写脚本的简单命令行工具。了解并使用这些设施。对文件进行版本控制的方式与对系统中所有其他代码进行版本控制的方式相同。

Configuration information associated with middleware is as much a part of the system as the programs written in your favorite programming language. Much modern middleware supports scriptable methods of configuration: XML configuration is common, and some supply simple command-line tools suitable for scripting. Learn about and utilize these facilities. Version-control the files in the same way as you version-control all the other code in your system.

如果您有选择,请选择具有这些功能的中间件。根据我们的经验,这些设施比最性感的管理工具甚至最新的标准合规性都重要得多。

If you have a choice, select middleware with these features. In our experience, these facilities are much more important than the sexiest administration tool or even the most recent level of standards compliance.

可悲的是,仍然有许多(通常是昂贵的)中间件产品,虽然旨在提供“企业级服务”,但在部署和配置管理的便利性方面有所下降。根据我们的经验,项目的成功通常取决于其干净可靠地部署的能力。

Sadly, there remain many (often expensive) middleware products that, while aiming to provide “enterprise-level services,” fall down in the ease of deployment and configuration management. In our experience, the success of a project can often turn on its ability to be deployed cleanly and reliably.

在我们看来,除非能够以自动化方式进行部署和配置,否则任何技术都不能被视为真正的企业就绪技术。如果您不能将重要的配置信息保存在版本化存储中,从而以受控方式管理更改,则该技术将成为交付高质量结果的障碍。过去我们曾多次被这种情况所困扰。

In our view, no technology can be considered genuinely enterprise-ready unless it is capable of being deployed and configured in an automated way. If you can’t keep vital configuration information in versioned storage and thus manage changes to in a controlled manner, the technology will become an obstacle to delivering high-quality results. We have been burnt by this many times in the past.


图片

当凌晨两点,你有一个关键的错误修复要发送到生产环境中时,将数据输入基于 GUI 的配置工具时很容易出错。在这种情况下,自动部署过程会拯救你。

When it is two o’clock in the morning, and you have a critical bugfix to send into production, it is far too easy to make a mistake when entering data into a GUIbased configuration tool. It is at times like this that an automated deployment procedure will save you.


通常,开源系统和组件在可编写脚本的配置方面处于领先地位。因此,针对基础设施问题的开源解决方案通常易于管理和集成。令人失望的是,软件行业的某些部分持有不同的观点。我们经常被要求从事我们没有自由选择的项目。那么,在您漂亮的模块化、可配置、版本化、自动化构建和部署过程中,当面对一个整体的系统块时,应采用哪些策略?

Often, open source systems and components lead the way in scriptable configuration. As a result, open source solutions to infrastructural problems are usually easy to manage and integrate. Disappointingly, some parts of the software industry take a different view. We are often asked to work on projects where we don’t have a free choice. So what are the strategies to employ when faced with a monolithic block of a system in the midst of your nice modular, configurable, versioned, automated build and deployment process?

研究产品

Research the Product

在寻找低成本、低能耗的解决方案时,显而易见的出发点是绝对确定所讨论的产品没有宣传不佳的自动配置选项。仔细阅读文档,专门寻找此类选项,在网上搜索建议,与产品的支持代表交谈,查看论坛或群组。简而言之,在您继续使用下面描述的其他策略之前,请确保没有更好的选择。

When looking for a low-cost, low-energy solution, the obvious starting point is to be absolutely certain that the product in question doesn’t have a poorly advertised automated configuration option. Read the documentation carefully, looking specifically for such options, search the web for advice, talk to your product’s support representatives, check on forums or groups. In short, make sure that there isn’t a better option before you move on to the other strategies described below.

奇怪的是,我们发现产品支持途径出乎意料地毫无帮助。毕竟,我们所要求的只是能够对我们投资于他们产品的工作进行版本控制。我们最喜欢来自一家大型供应商的回应是,“哦,是的,我们将在下一个版本中将我们自己的版本控制构建到系统中。” 即使他们这样做了,即使在一两年后拥有该功能可能会对我们当时正在进行的项目产生任何影响,集成到一个粗糙的、专有的版本控制系统也不会帮助我们管理一个一致的配置集。

Strangely, we have found the product support route surprisingly unhelpful. After all, all we are asking for is the ability to version-control the work that we invest in their product. Our favorite response from one large vendor was, “Oh yes, we are going to build our own version control into the system in the release after next.” Even if they had done so, and even if having the feature a year or two later could make any difference to the project we were working on at the time, integrating to a crude, proprietary version control system wouldn’t have helped us manage a consistent configuration set.

检查你的中间件如何处理状态

Examine How Your Middleware Handles State

如果您确定您的中间件不支持任何形式的自动配置,下一步就是看看您是否可以通过在背后对其存储进行版本控制来作弊。如今,许多产品都使用 XML 文件来存储它们的配置信息。这些文件在现代版本控制系统中工作得非常好,几乎没有问题。如果第三方系统将其状态存储在二进制文件中,请考虑对这些二进制文件进行修订控制。随着开发的进行,它们通常会经常更改。

If you are certain that your middleware does not support any form of automated configuration, the next step is to see if you can cheat by version-controlling its storage behind its back. Many products these days use XML files to store their configuration information. Such files work extremely well with modern version control systems and present few problems. If the third-party system stores its state in binary files, consider revision-controlling these binaries. They will usually change frequently as the development progresses.

在大多数情况下,使用任何类型的平面文件为您的产品提供配置信息时,您将面临的主要问题是如何以及何时产品读取相关配置信息。在一些自动化友好的情况下,只需将文件的新版本复制到正确的位置就足够了。如果可行,您可以更进一步,将产品的二进制文件与其配置分开。在这种情况下,有必要对安装过程进行逆向工程,本质上是编写您自己的安装程序。您将需要查看应用程序安装其二进制文件和库的位置。

In most cases, where flat files of any kind are used to supply configuration information to your product, the principal issue that you will face is how and when the product reads the relevant configuration information. In a few automationfriendly cases, simply copying the new versions of the files into the correct location will suffice. If this works, you can go further and separate your product’s binaries from their configuration. In this case, it is necessary to reverse-engineer the installation process and, essentially, write your own installer. You will need to look and see where the application installs its binaries and libraries.

然后你有两个选择。最简单的选择是将相关的二进制文件与将它们安装到相关环境的脚本一起存储在版本控制中。选项二是真正开始并编写您自己的安装程序(或者,如果您使用的是 RedHat 派生的 Linux 发行版,则编写诸如 RPM 之类的软件包)。创建 RPM(或其他安装程序,就此而言)并不难,根据您的情况,这可能是值得的。然后,您可以使用安装程序将产品部署到新环境,并应用版本控制中的配置。

You then have two options. The simplest option is to store the relevant binaries in version control along with a script that installs them to the relevant environment. Option two is to really go ahead and write your own installer (or a package such as an RPM if you’re using a RedHat-derived Linux distribution, for example). Creating RPMs (or other installers, for that matter) is not that hard, and it might be well worth the trouble, depending on your circumstances. You can then deploy your product to a new environment using your installers, and apply the configuration from version control.

一些产品使用数据库来存储它们的配置信息。此类产品通常具有复杂的管理控制台,隐藏了它们存储的信息的复杂性。这些产品对自动化环境管理提出了特别的困难。您基本上必须将数据库视为一个 blob。但是,至少您的供应商应该提供备份和恢复数据库的说明。如果是这样,您当然应该创建一个自动化流程来执行此操作。然后就可以进行备份,弄清楚如何操作其数据,然后用您的更改将其恢复。

Some products use databases to store their configuration information. Such products usually have sophisticated administration consoles that hide the complexities of the information that they store. These products present particular difficulties for automated environment management. You basically have to treat the database as a blob. However, at the very least your vendor should provide instructions for backing up and restoring the database. If so, you should certainly create an automated process to do this. It may then be possible to take the backup, work out how to manipulate its data, and then restore it back with your changes.

寻找配置 API

Look for a Configuration API

我们在这里讨论的类别中的许多产品都支持一种或另一种形式的编程接口。有些可能允许您充分配置系统以满足您的需要。一种策略是为您正在使用的系统定义您自己的简单配置文件。创建自定义构建任务来解释这些脚本并使用 API 来配置系统。这种“发明你自己的”配置文件的策略将配置管理重新交到你的手中——允许你对配置文件进行版本控制并自动使用它们。Microsoft 的 IIS 是我们过去使用过这种方法的一个系统,使用它的 XML 元数据库。但是,较新版本的 IIS 允许通过 PowerShell 编写脚本。

Many products in the class we’re discussing here support programming interfaces in one form or another. Some may allow you to configure the system sufficiently to meet your needs. One strategy is to define your own simple configuration file for the system that you are working with. Create custom build tasks to interpret those scripts and to use the API to configure the system. This strategy of “invent your own” configuration files puts configuration management back into your hands—allowing you to version-control the configuration files and automate their use. Microsoft’s IIS is one system where we have used this approach in the past, using its XML metabase. However, newer versions of IIS allow for scripting via PowerShell.

使用更好的技术

Use a Better Technology

理论上,您可以尝试一些其他方法——例如,创建您自己的版本控制友好的配置信息并编写代码以通过任何现有方式将其映射到您产品的本机配置中,例如通过管理回放用户交互控制台或逆向工程数据库结构。实际上,我们还没有达到这一点观点。我们有几次很接近,但通常会找到允许我们做我们需要做的事情的 API。

Theoretically, you could try some other approaches—for example, creating your own version-control-friendly configuration information and writing code to map it into the native configuration of you product via whatever means present themselves, such as playing back user interactions through the admin console or reverse-engineering the database structure. In reality, we haven’t yet reached this point. We came close a couple of times, but then usually found APIs allowing us to do what we need.

虽然可以对基础设施产品的二进制文件格式甚至数据库模式进行逆向工程,但您应该检查这样做是否会违反许可协议的条款。如果您发现自己处于这种极端状态,那么值得询问供应商是否可以提供帮助,或许可以提供分享您生产的任何技术以向他们提供一些好处作为回报。一些供应商(尤其是较小的供应商)对这类事情相当了解,因此值得一试。但是,由于难以支持这样的解决方案,许多人不会感兴趣。如果是这样,此时我们强烈建议采用更易于处理的替代技术。

While it is possible to reverse-engineer the binary file formats or even database schemas of your infrastructure products, you should check if doing so will breach the terms of your license agreement. If you find yourselves at this extreme, it is worth asking the vendors if they can help, perhaps offering to share any technology that you produce to offer them some benefit in return. Some vendors (particularly smaller ones) are reasonably enlightened about this kind of thing, so it’s worth a try. However, many will not be interested because of the difficulty of supporting such a solution. If so, at this point we would strongly recommend adopting an alternate technology which is more tractable.

许多组织对更改他们使用的软件平台持谨慎态度,因为他们已经在上面花费了大量资金。然而,这种被称为沉没成本谬误的论点并没有考虑到转向更先进技术所损失的机会成本。试着找一个足够资深的人,或者一个友好的审计师,来了解你正在遭受的效率损失的财务后果,并让他们投资于更好的替代方案。在我们的一个项目中,我们保留了一份“痛苦登记册”,这是一份记录在低效技术上浪费时间的日记,一个月后很容易证明与减缓交付的技术作斗争的成本。

Many organizations are wary about changing the software platform that they use because they have already spent a great deal of money on it. However, this argument, known as the sunk cost fallacy, does not take into account the lost opportunity cost of moving to a superior technology. Try to get someone sufficiently senior, or a friendly auditor, to understand the financial ramifications of the loss of efficiency that you are suffering and get them to invest in a superior alternative. On one of our projects, we kept a “pain-register,” a diary of time lost on inefficient technology, which after a month easily demonstrated the cost of struggling with technology that slowed down delivery.

管理基础设施服务

Managing Infrastructure Services

基础设施服务(例如路由器、DNS 和目录服务)的问题非常常见,会破坏生产环境中的软件,而这些软件在整个部署管道中都能完美运行。Michael Nygard 为 InfoQ 写了一篇文章,其中讲述了一个系统每天都在同一时间神秘死亡的故事 [bhc2vR]。问题原来是防火墙在一小时后丢弃了不活动的 TCP 连接。由于系统在晚上空闲,当早上开始活动时,来自池数据库连接的 TCP 数据包将被防火墙静默丢弃。

It is extremely common for problems with infrastructure services—such as routers, DNS, and directory services—to break software in production environments that worked perfectly all through the deployment pipeline. Michael Nygard wrote an article for InfoQ in which he tells the story of a system which died mysteriously at the same time every day [bhc2vR]. The problem turned out to be a firewall which dropped inactive TCP connections after one hour. As the system was idle at night, when activity started in the morning, the TCP packets from the pooled database connections would be dropped silently by the firewall.

像这样的问题会发生在你身上,当它们发生时,它们将非常难以诊断。尽管网络历史悠久,但很少有人真正了解整个 TCP/IP 堆栈的来龙去脉(以及某些基础设施(如防火墙)如何打破规则),尤其是当多个不同的实现在同一网络中共存时。这是生产环境中的常见情况。

Problems like this will happen to you, and when they do, they will be maddeningly difficult to diagnose. Although networking has a long history, very few people really understand the ins and outs of the entire TCP/IP stack (and how some infrastructure, such as firewalls, can break the rules), especially when several different implementations coexist on the same network. This is the usual situation in production environments.

我们为您提供了几条建议。

We have several pieces of advice for you.

• 网络基础设施配置的每个部分,从 DNS 区域文件到 DHCP,再到防火墙和路由器配置,再到 SMTP 和您的应用程序所依赖的其他服务,都应该是版本控制的。使用 Puppet 之类的工具将配置从版本控制推送到您的系统,以便它们是自治的,并且您知道除了通过在版本控制中更改配置文件外,没有其他方法可以引入更改。

• Every part of your networking infrastructure’s configuration, from DNS zone files to DHCP to firewall and router configurations to SMTP and other services your applications rely on, should be version-controlled. Use a tool like Puppet to push configuration out from version control to your systems so that they are autonomic, and you know that there is no other way to introduce changes except via changing a configuration file in version control.

• 安装一个好的网络监控系统,例如Nagios、OpenNMS、HP Operations Manager 或它们的兄弟之一。确保您知道网络连接何时断开,并监控应用程序使用的每条路由上的每个端口。第 317 页的“监控基础设施和应用程序”部分更详细地讨论了监控

• Install a good network monitoring system such as Nagios, OpenNMS, HP Operations Manager, or one of their brethren. Make sure that you know when network connectivity is broken, and monitor every port on every route that your application uses. Monitoring is discussed at more length in the “Monitoring Infrastructure and Applications” section on page 317.

• 日志记录是您的朋友。每次网络连接超时或发现意外关闭时,您的应用程序都应在WARNING级别登录。每次关闭连接时,您都应该在INFODEBUG级别登录,如果日志太冗长的话。您应该在DEBUG级别记录您打开的每个连接,包括连接端点上尽可能多的信息。

• Logging is your friend. Your applications should log at WARNING level every time a network connection times out or is found to be unexpectedly closed. You should log at INFO or, if the logs are too verbose, DEBUG level every time you close a connection. You should log at DEBUG level every connection that you open, including as much information as possible on the endpoint of the connection.

• 确保您的冒烟测试在部署时检查所有连接,以排除任何路由或连接问题。

• Make sure that your smoke tests check all of the connections at deployment time to flush out any routing or connectivity problems.

• 使您的集成测试环境的网络拓扑尽可能类似于生产环境,包括使用相同的硬件,它们之间具有相同的物理连接(直至使用完全相同的插座和相同部件号的电缆)。如果发生硬件故障,如此构建的环境可以有效地用作备份环境。事实上,许多企业都有一个称为 staging 的环境,它具有双重目的,即准确复制生产,以便可以测试生产部署,并充当故障转移。在第 261 页的“蓝绿部署”部分中描述的蓝绿部署模式允许您执行此操作,即使您只有一个物理环境。

• Make your integration testing environment’s network topology as similar as possible to production, including using the same pieces of hardware with the same physical connections between them (down to the level of using exactly the same sockets and the same part number of cable). An environment so constructed can usefully serve as a backup environment should a hardware failure occur. Indeed, many enterprises have an environment known as staging which serves the dual purpose of both exactly replicating production, so that the production deployment can be tested, and acting as a failover. The blue-green deployment pattern, described in the “Blue-Green Deployments” section on page 261, allows you to do this even if you have only one physical environment.

最后,当出现问题时,可以使用取证工具。Wireshark 和 Tcpdump 都是非常有用的工具,可以很容易地看到飞过的数据包,并过滤它们,这样您就可以准确地隔离出您正在寻找的数据包。UNIX 工具 Lsof 及其 Windows 同类工具 Handle 和 TCPView(Sysinternals 套件的一部分)也可以非常方便地查看您的计算机上打开了哪些文件和套接字。

Finally, when something does go wrong, have forensic tools available. Wireshark and Tcpdump are both fantastically useful tools that make it easy to see packets flying past, and filter them so you can isolate exactly the packets you’re looking for. The UNIX tool Lsof and its Windows cousins Handle and TCPView (part of the Sysinternals suite) also come in very handy to see what files and sockets are open on your machine.

多宿主系统

Multihomed Systems

图 11.3 多宿主服务器

Figure 11.3 Multihomed servers

图片

生产系统的一项重要强化措施是针对不同类型的流量使用多个隔离网络,并结合多宿主服务器。多宿主服务器有多个网络接口,每个接口都与不同的网络通信。至少,你可能有一个网络监控和管理生产服务器、用于运行备份的网络以及用于将生产数据移入和移出服务器的网络。这样的拓扑如图 11.3所示。

One important piece of hardening on production systems is the use of multiple, isolated networks for different types of traffic, in conjunction with multihomed servers. Multihomed servers have multiple network interfaces, each of which talks to a different network. At a minimum, you might have a network for monitoring and administering production servers, a network for running backups, and a network for production data to move to and from your servers. Such a topology is shown in Figure 11.3.

出于安全原因,管理网络与生产网络在物理上是分开的。通常,控制和监视生产服务器所需的任何服务(例如 ssh 或 SNMP)都将配置为仅绑定到 nic2,因此无法从生产网络访问这些服务。备份网络与生产网络在物理上是分开的,因此备份期间移动的大量数据不会影响性能或管理网络。高可用性和高性能系统有时将多个 NIC 用于生产数据,用于故障转移或专用服务——例如,您可能有一个单独的专用网络用于您组织的消息总线或数据库。

The administration network is physically separated from the production network for security reasons. Typically, any services such as ssh or SNMP required to control and monitor production servers would be configured to bind only to the nic2, so it is impossible to access these services from the production network. The backup network is physically separated from the production network, so that the large volumes of data that move during backups don’t affect the performance or administration networks. High-availability and highperformance systems sometimes use multiple NICs for production data, either for failover or for dedicated services—for example, you might have a separate, dedicated network for your organization’s message bus or database.

首先,重要的是要确保在多宿主机上运行的每个服务和应用程序只绑定到相关的 NIC。特别是,应用程序开发人员需要使他们的应用程序侦听的 IP 地址在部署时可配置。

First, it’s important to ensure that each service and application running on a multihomed box binds only to the relevant NICs. In particular, application developers need to make the IP addresses that their application listens on configurable at deploy time.

其次,应该集中管理和监控多宿主网络配置的所有配置(包括路由)。很容易犯需要访问数据中心的错误——例如,当 Jez 在其职业生涯的早期,将生产机器上的管理 NIC 关闭,而忘记了他是在 sshed 中而不是在物理 tty 上。正如尼加德指出的那样,3这是还可能造成更严重的路由错误,例如允许从多宿主机上的一个 NIC 到另一个 NIC 的流量,可能会造成安全漏洞,例如暴露客户数据。

Second, all the configuration (including routing) for a multihomed network configuration should be managed and monitored centrally. It’s very easy to make mistakes that require a visit to the data center—such as when Jez, early on in his career, brought down an administration NIC on a production box, forgetting that he was sshed in rather than on a physical tty. As Nygard points out,3 it’s also possible to make more serious routing errors, such as allowing traffic from one NIC on a multihomed box through to another, potentially creating security breaches such as exposing customer data.

虚拟化

Virtualization

我们已经讨论了当环境不同时出现的问题,因为服务器是艺术品。虚拟化提供了一种方法来放大本章中已经描述的技术的好处,用于自动配置服务器和环境。

We have already discussed the problems that occur when environments differ because servers are works of art. Virtualization provides a way to amplify the benefits of the techniques, already described in this chapter, for automating the provisioning of servers and environments.

在这里,我们将描述环境虚拟化的使用,以帮助提供受控的、完全可重复的部署和发布过程。虚拟化可以通过多种方式帮助减少部署软件所需的时间以及与之相关的风险。在部署中使用虚拟机极大地有助于在系统的广度和深度上实现有效的配置管理。

Here we will describe the use of environment virtualization to help provide a controlled, fully repeatable deployment and release process. Virtualization can help reduce the time it takes to deploy software, and the risks associated with it, in a variety of ways. The use of virtual machines in deployment is an enormous aid to achieving effective configuration management across the breadth and depth of your systems.

特别是,虚拟化提供了以下好处:

In particular, virtualization provides the following benefits:

快速响应不断变化的需求需要一个新的测试环境?一个新的虚拟机可以在几秒钟内免费配置,而不是几天或几周以获得新的物理环境。显然,您不能在单个主机上运行无限数量的 VM,但在某些情况下使用虚拟化可以将购买新硬件的需求与它们运行的​​环境的生命周期分离开来。

Fast response to changing requirements. Need a new testing environment? A new virtual machine can be provisioned in seconds at no cost, versus days or weeks to obtain a new physical environment. Obviously, you cannot run an infinite number of VMs on a single host—but using virtualization can in some situations decouple the need to buy new hardware from the lifecycle of the environments that they run.

合并当组织相对不成熟时,每个团队通常会有自己的 CI 服务器和测试环境,它们位于办公桌下的物理盒子上。虚拟化使得整合 CI 和测试基础架构变得容易,因此它可以作为服务提供给交付团队。它在硬件使用方面也更有效率。

Consolidation. When organizations are relatively immature, each team will often have its own CI servers and testing environments sitting on physical boxes under their desks. Virtualization makes it easy to consolidate CI and testing infrastructure so it can be offered as a service to delivery teams. It is also more efficient in terms of hardware usage.

硬件标准化应用程序的组件和子系统之间的功能差异不再迫使您维护不同的硬件配置,每个硬件配置都有自己的规范。虚拟化允许您在物理环境的单一硬件配置上进行标准化,但虚拟地运行各种异构环境和平台。

Standardizing hardware. Functional differences between components and subsystems of your application no longer force you to maintain distinct hardware configurations, each with its own specification. Virtualization allows you to standardize on a single hardware configuration for physical environments but run a variety of heterogeneous environments and platforms virtually.

更易于维护的基线您可以维护一个基线映像库(操作系统加上应用程序堆栈)甚至环境,然后单击按钮将它们推送到集群。

Easier-to-maintain baselines. You can maintain a library of baseline images—operating system plus application stacks—or even environments, and push them out to a cluster at the click of a button.

当应用于部署管道时,维护和配置新环境的简单性最为有用。

It is the simplicity of maintaining and provisioning new environments that is most useful when applied to the deployment pipeline.

• 虚拟化提供了一种简单的机制来为系统运行的环境创建基准。您可以创建和调整将应用程序托管为虚拟服务器的环境,一旦您对结果感到满意,就可以保存图像和配置,然后继续创建任意数量的副本,知道您在做什么得到的是原作的忠实克隆。

• Virtualization provides a simple mechanism to create a baseline for the environments in which your systems operate. You can create and tune the environments that host your applications as virtual servers, and once you are happy with the result, you can save the images and configuration and then go on to create as many copies as you wish, knowing that what you’re getting are faithful clones of the original.

• 由于构建主机的服务器映像存储在库中,并且可以与应用程序的特定构建相关联,因此可以轻松地将任何环境恢复到以前的状态——不仅是应用程序,还包括应用程序的各个方面。您部署的软件。

• Since the server images from which your hosts are built are stored in a library and can be associated with a particular build of your application, it is simple to revert any environment back to a previous state—not just the application but every aspect of the software that you deploy.

• 使用虚拟服务器作为主机环境的基线,可以轻松创建生产环境的副本,即使生产环境由多台服务器组成,也可以轻松复制它们以进行测试。现代虚拟化软件提供了很大程度的灵活性,允许以编程方式控制系统的某些方面,例如网络拓扑。

• The use of virtual servers to baseline host environments makes it simple to create copies of production environments, even where a production environment consists of several servers, and to reproduce them for testing purposes. Modern virtualization software offers a significant degree of flexibility, allowing some aspects of the system, like network topology, to be controlled programmatically.

• 它是最后一个缺失的部分,允许对您的应用程序的任何构建进行真正的按钮部署。如果你需要一个新的环境来演示对于潜在客户的最新功能,您可以在早上创建新环境,在午餐时间运行演示,并在下午删除它。

• It is the last missing piece that allows for true push-button deployments of any build of your application. If you need a new environment to demonstrate the latest features for a potential customer, you can create the new environment in the morning, run the demonstration at lunchtime, and delete it in the afternoon.

虚拟化还提高了我们测试功能和非功能需求的能力。

Virtualization also improves our ability to test both functional and nonfunctional requirements.

• VMM 提供对系统功能的编程控制,例如网络连接。这使得非功能性需求(例如可用性)的测试变得更加容易并且能够自动化。例如,通过以编程方式断开一个或多个节点并观察对系统的影响来测试一组服务器的行为相对简单。

• VMMs provide programmatic control of features of your system, such as network connectivity. This makes the testing of nonfunctional requirements, such as availability, much easier and capable of automation. For example, it is relatively straightforward to test the behavior of a cluster of servers by disconnecting one or more of the nodes programmatically and observing the effect on system.

• 虚拟化还提供了显着加速长时间运行测试的能力。您可以在虚拟机的构建网格上并行运行它们,而不是在单个机器上运行它们。我们经常在我们的项目中这样做。在我们的一个较大的机器上,它将运行测试的时间从 13 小时减少到 45 分钟。

• Virtualization also provides the capability to significantly speed up long-running tests. Instead of running them on a single box, you can run them in parallel on a build grid of virtual machines. We routinely do this on our projects. On one of our larger ones, it reduced the time to run our tests from 13 hours to 45 minutes.

管理虚拟环境

Managing Virtual Environments

VMM 最重要的特征之一是虚拟机映像是单个文件。这样的文件称为磁盘映像。磁盘映像的有用之处在于您可以复制它们并对其进行版本控制(可能不在版本控制中,除非您的 VCS 可以处理大量非常大的二进制文件)。然后您可以将它们用作模板或基线(在配置管理术语中)。某些 VMM 将模板视为与磁盘映像不同的东西,但最终它们是同一回事。许多 VMM 甚至允许您从正在运行的 VM 创建模板。然后,您可以在几秒钟内从这些模板中创建任意数量的运行实例。

One of the most important characteristics of VMMs is that a virtual machine image is a single file. Such a file is called a disk image. The useful thing about disk images is that you can copy them and version them (probably not in version control, unless your VCS can handle lots of very large binary files). You can then use them as templates, or baselines (in configuration management terminology). Some VMMs treat templates as something different from disk images, but ultimately they’re the same thing. Many VMMs even allow you to create templates from running VMs. You can then create as many running instances as you want from these templates in seconds.

一些 VMM 供应商提供的另一个有用工具是拍摄物理框的快照并将其转换为磁盘映像。这非常有用,因为这意味着您可以在生产环境中复制这些盒子,将它们另存为模板,然后启动生产环境的副本以进行持续集成和测试。

Another useful tool some VMM vendors provide is taking a snapshot of a physical box and turning it into a disk image. This is incredibly useful, because it means you can take a copy of the boxes in your production environment, save them as templates, and fire up copies of your production environment to do continuous integration and testing on.

图 11.4 从模板创建虚拟环境

Figure 11.4 Creating virtual environments from templates

图片

在本章的前面,我们讨论了如何使用完全自动化的过程来配置新环境。如果您拥有虚拟化基础架构,则可以创建这样配置的服务器的磁盘映像,并将其用作每个使用相同配置的服务器的模板。或者,您可以使用像 rPath 的 rBuilder 这样的工具来为您创建和管理基线。为您的环境中所需的每种类型的机器创建模板后,您可以使用 VMM 软件根据需要从您的模板启动新环境。图 11.4演示了这一点。

Earlier in this chapter, we discussed how to provision new environments using a completely automated process. If you have a virtualized infrastructure, you can create a disk image of a server thus provisioned and use it as a template for every server that uses the same configuration. Alternatively, you could use a tool like rPath’s rBuilder to create and manage baselines for you. Once you have templates for every type of machine you need in your environment, you can use your VMM software to start up new environments from your templates as needed. Figure 11.4 demonstrates this.

这些模板构成了环境的基线和已知良好版本,您可以在其上进行其余的配置和部署。我们简单地满足了我们的要求,即提供新环境应该比调试和修复由于不受控制的更改而处于未知状态的环境更快——您只需拉下有缺陷的 VM,然后从基线模板启动一个新环境。

These templates form the baselines, known-good versions of your environments, on which the rest of your configuration and deployment can be made. We trivially satisfy our requirement that it should be quicker to provision a new environment than debug and fix one that’s in an unknown state due to uncontrolled changes—you just pull down the defective VM and start a new one from the baseline template.

现在可以以增量方式实现环境供应的自动化过程。不必总是从头开始,您可以从一个已知良好的基准映像开始您的配置过程,该映像可能只安装了基本操作系统。您仍然应该在每个模板上为您的数据中心自动化工具(下图 11.5中的 Puppet)安装一个代理,以便您的虚拟机是自主的,并且可以在整个系统中一致地推出更改。

It now becomes possible to implement an automated process for environment provision in an incremental way. Instead of always starting from scratch, you can start your provisioning process from a known-good baseline image, which may have only the base operating system installed. You should still install an agent for your data center automation tool (Puppet in Figure 11.5 below) on every template so that your virtual machines are autonomic, and so that changes can be rolled out consistently across your entire system.

图 11.5 创建虚拟机模板

Figure 11.5 Creating VM templates

图片

然后,您可以运行自动化过程来配置操作系统并安装和配置应用程序所需的任何软件。再次,在这一点上,将环境中每种类型的框的副本保存为基线。图 11.5描述了这个工作流

You can then run your automated process to configure the operating system and install and configure any software required by your applications. Once again, at this point, save a copy of each type of box in your environment as a baseline. This workflow is described in Figure 11.5.

虚拟化还使本章前面讨论的另外两个棘手的场景更易于管理:处理以不受控制的方式发展的环境,以及处理堆栈中无法以自动化方式管理的软件。

Virtualization also makes two other intractable scenarios, discussed earlier in the chapter, much easier to manage: dealing with environments that have evolved in an uncontrolled way, and dealing with software in your stack that can’t be managed in an automated fashion.

通过未记录或记录不当的手动更改(包括遗留系统)演变而来的环境在每个组织中都存在问题。如果这些艺术作品出现故障,调试它们将极其困难,并且几乎不可能复制它们用于测试目的。如果建立和管理它们的人离开或去度假并且出现问题,你就有麻烦了。更改此类系统的风险也很大。

Environments that have evolved through undocumented or poorly documented manual changes, including legacy systems, present a problem in every organization. If these works of art malfunction, it is extremely hard to debug them, and it is virtually impossible to make copies of them for testing purposes. If the people who set them up and manage them leave or go on holiday and something goes wrong, you are in trouble. It is also very risky to make changes to such systems.

虚拟化提供了一种减轻这种风险的方法。使用您的虚拟化软件为正在运行的机器或构成环境的机器拍摄快照,并将它们变成虚拟机。然后,您可以轻松地创建环境副本以用于测试目的。

Virtualization provides a way to mitigate this risk. Use your virtualization software to take a snapshot of the running machine or machines that comprise the environment and turn them into VMs. You can then easily create copies of the environment for testing purposes.

这种技术提供了一种有价值的方法,可以逐步从手动管理环境过渡到自动化方法。与其从头开始自动化您的配置过程,不如根据您当前已知良好的系统创建模板。同样,您可以用虚拟环境替换真实环境以确认您的模板是好的。

This technique provides a valuable way to move incrementally from managing environments manually to an automated approach. Instead of automating your provisioning process from scratch, create templates based on your current knowngood systems. Again, you can replace the real environments with the virtual ones to confirm that your templates are good.

最后,虚拟化提供了一种方法来处理您的应用程序所依赖的无法以自动方式安装或配置的软件,包括 COTS。只需在虚拟机上手动安装和配置软件,然后从中创建模板。然后,这可以作为您可以在需要时复制的基线。

Finally, virtualization provides a way to deal with software that your application relies on that cannot be installed or configured in an automated way, including COTS. Simply install and configure the software manually on a virtual machine, and then create a template from it. This can then serve as a baseline that you can replicate as and when you require.

如果您以这种方式管理环境,则必须跟踪基线版本。每次对基线进行更改时,都需要将其存储为新版本,正如我们之前所说,以针对最新的候选发布版本重新运行基于该基线的所有管道阶段。您还需要能够将特定基线版本的使用与已知在每个环境中运行的应用程序版本相关联,这将我们带到下一部分。

If you manage environments in this way, it is essential to keep track of baseline versions. Every time you make a change to a baseline, you need to store it as a new version, and as we have said previously, to rerun all of the pipeline stages that are based on that baseline against the most recent release candidate. You also need to be able to correlate the use of specific baseline versions with the versions of your applications that are known to run on them for every environment, which brings us to the next section.

虚拟环境和部署管道

Virtual Environments and the Deployment Pipeline

图 11.6 一个简单的管道

Figure 11.6 A simple pipeline

图片

部署管道的目的是通过自动构建、部署和测试过程对您对应用程序所做的每项更改进行验证,以验证其是否适合发布。图 11.6显示了一个简单的管道

The purpose of the deployment pipeline is to put every change you make to your application through your automated build, deploy, and test process to verify its fitness for release. A simple pipeline is shown in Figure 11.6.

管道方法的一些特性值得重新审视,以考虑如何在虚拟化环境中使用它们。

There are some features of the pipeline approach that are worth revisiting to consider how to use them in the context of virtualization.

• 管道的每个实例都与触发它的版本控制更改相关联。

• Each instance of a pipeline is associated with a change in version control that triggered it.

• 提交阶段之后的每个管道阶段都应该在类似生产的环境中运行。

• Every stage of the pipeline subsequent to the commit stage should be run in a production-like environment.

• 使用完全相同的二进制文件的完全相同的部署过程应该在每个环境中运行——环境之间的差异应该被捕获为配置信息。

• Exactly the same deployment process using exactly the same binaries should be run in every environment—differences between environments should be captured as configuration information.

可以看出,管道中正在测试的不仅仅是应用程序。事实上,当管道中出现测试失败时,首先发生的事情是分类以确定失败的原因。五个最可能导致失败的原因是

It can be seen that what is being tested in the pipeline is not just the application. Indeed when there is a test failure in the pipeline, the first thing that happens is triage to determine the cause of the failure. The five most likely causes of a failure are

• 应用程序代码中的错误

• A bug in the application’s code

• 测试中的错误或无效期望

• A bug or invalid expectation in a test

• 应用程序配置问题

• A problem with the application’s configuration

• 部署过程有问题

• A problem with the deployment process

• 环境问题

• A problem with the environment

因此,环境的配置代表配置空间中的自由度之一。因此,您的应用程序的已知良好版本不仅与作为二进制代码、自动化测试、部署脚本和配置源的版本控制系统中的修订号相关。您的应用程序的已知良好版本还与管道实例所在的环境配置相关跑了。即使它在多个环境中运行,它们也应该具有完全相同的类似生产的配置。

Thus the configuration of the environment represents one of the degrees of freedom in configuration space. It follows that a known-good version of your application is not just correlated with the revision numbers in the version control system that were the source of the binary code, automated tests, deployment scripts, and configuration. A known-good version of your application is also correlated with the configuration of the environment that the pipeline instance ran on. Even if it ran on multiple environments, they should all have had exactly the same production-like configuration.

发布到生产环境时,您应该使用与运行所有测试完全相同的环境。所有这一切的必然结果是,对环境配置的更改应该触发新的管道实例,就像任何其他更改一样(到源代码、测试、脚本等)。您的构建和发布管理系统应该能够记住您用于运行管道的 VM 模板集,并且能够在您部署到生产环境时从该模板集开始。

When releasing to production, you should use precisely the same environment that you ran all of your tests in. The corollary of all this is that a change in configuration to your environment should trigger a new pipeline instance the same way as any other change (to source code, tests, scripts, and so on). Your build and release management system should be able to remember the set of VM templates that you used to run the pipeline and be able to start exactly from that set of templates when you deploy to production.

图 11.7 通过部署管道的更改

Figure 11.7 Changes passing through the deployment pipeline

图片

在此示例中,您可以看到触发新发布候选的更改,以及发布候选在部署管道中的进展。首先,对源代码进行了更改;也许开发人员检查错误修复或新功能的部分实现。更改破坏了应用程序;提交阶段的测试失败,通知开发人员该缺陷。开发人员修复了缺陷并再次签入。这会触发一个新的构建,它会通过自动化测试(提交阶段、验收测试阶段、容量测试阶段)。接下来,操作人员希望在生产环境中测试对软件的升级。她使用升级后的软件创建了一个新的 VM 模板。这会触发一个新的管道实例,并且验收测试失败。我们的运营人员与开发人员一起寻找问题的根源(可能是某些配置设置)并修复它。这一次,应用程序在新环境中运行,通过了所有的自动和手动测试。应用程序及其测试的环境基线已准备好部署到生产环境中。

In this example, you can see changes triggering a new release candidate, and the release candidate’s progression through the deployment pipeline. First, a change is made to the source code; perhaps a developer checks in a bugfix or part of the implementation of a new feature. The change breaks the application; a test in the commit stage fails, notifying the developers of the defect. The developer fixes the defect and checks in again. This triggers a new build, which passes the automated tests (commit stage, acceptance test stage, capacity test stage). Next, an operations person wishes to test an upgrade to a piece of software in the production environment. She creates a new VM template with the upgraded software. This triggers a new pipeline instance, and the acceptance tests fail. Our operations person works with the developer to find the source of the problem (perhaps some configuration setting) and fixes it. This time, the application works with the new environment, passing all of its automated and manual tests. The application, along with the environment baseline it was tested on, is ready to be deployed into production.

当然,当应用程序部署到 UAT 和生产时,它使用与运行验收和容量测试完全相同的 VM 模板。这验证了使用此版本应用程序的环境的这种精确配置具有可接受的容量并且没有缺陷。希望这个例子展示了使用虚拟化的力量。

Of course when the application is deployed to UAT and production, it uses exactly the same VM template that was used to run the acceptance and capacity tests. This verifies that this precise configuration of the environment with this version of the application have an acceptable capacity and are defect-free. Hopefully, this example demonstrates the power of using virtualization.

但是,通过获取虚拟基线的副本并创建新基线来对暂存和生产环境进行每次更改并不是一个好主意。如果这样做,您不仅会很快用完磁盘空间;您将失去通过声明式、版本控制的配置管理的自主基础设施的好处。最好保持一个相对稳定的 VM 基线映像——一个带有最新服务包的基本操作系统映像、任何中间件或其他软件依赖项,以及安装的数据中心管理工具代理。然后,该工具用于完成供应过程并将基线精确地设置为所需的正确配置。

However, it is not a good idea to make every change to your staging and production environments by taking a copy of a virtual baseline and creating a new one. Not only will you get through disk space very quickly if you do this; you will lose the benefits of autonomic infrastructure managed through declarative, version-controlled configuration. It is better to keep a relatively stable VM baseline image—a basic operating system image with the latest service packs, any bits of middleware or other software dependencies, and an agent for your data center management tool installed. Then, the tool is used to complete the provisioning process and bring the baseline to exactly the right configuration required.

使用虚拟环境进行高度并行测试

Highly Parallel Testing with Virtual Environments

对于用户安装的软件,情况有所不同,尤其是在公司环境之外。在这种情况下,您通常无法控制生产环境,因为它是用户的计算机。在这种情况下,在各种“类生产”环境中测试您的软件就变得很重要。例如,桌面应用程序通常必须是多平台的,在 Linux、Mac OS 和 Windows 上运行,并且通常在每个平台的几个不同版本和配置上运行。

Things are somewhat different in the case of user-installed software, particularly outside of a corporate environment. In this case, you normally don’t have much control over the production environment, because it is the user’s computer. In this case, it becomes important to test your software on a wide variety of “production-like” environments. For example, desktop applications often have to be multiplatform, running on Linux, Mac OS, and Windows, and usually on several different versions and configurations of each of these platforms.

虚拟化提供了处理多平台测试的绝佳方式。只需使用您的应用程序面向的每个潜在环境的示例创建虚拟机,并从中创建 VM 模板。然后并行运行管道中的所有阶段(接受、容量、UAT)。现代持续集成工具使这种方法变得简单明了。

Virtualization provides an excellent way to handle multiplatform testing. Simply create virtual machines with examples of each of the potential environments that your application targets, and create VM templates from them. Then run all of the stages in your pipeline (acceptance, capacity, UAT) on all of them in parallel. Modern continuous integration tools make this approach straightforward.

您可以使用相同的技术来并行化测试,以缩短昂贵的验收和容量测试的重要反馈周期。假设您的测试都是独立的(请参阅我们在第 218 页的“验收测试性能”部分中的建议),您可以在多个虚拟机上并行运行它们(当然您也可以将它们作为单独的线程并行运行,但是有是这个尺度的限制)。这种为您的构建创建专用计算网格的方法可以大大加快运行自动化测试的速度。最终,测试性能仅受单个最慢测试用例运行时间和硬件预算大小的限制。同样,像 Selenium Grid 这样的现代 CI 工具和软件使这一切变得非常简单。

You can use the same technique to parallelize tests to shorten the vital feedback cycle of expensive acceptance and capacity tests. Assuming that your tests are all independent (see our advice in the “Acceptance Test Performance” section on page 218), you can run them in parallel on multiple virtual machines (of course you could also run them in parallel as separate threads, but there is a limit to how well this scales). This approach to creating a dedicated compute grid for your build can vastly speed up running your automated tests. Ultimately, the performance of your tests is only limited by the time it takes for your single slowest test case to run and the size of your hardware budget. Again, modern CI tools and software like Selenium Grid make this very simple.

云计算

Cloud Computing

云计算是一个古老的想法,但近年来它已经无处不在。在云计算中,信息存储在 Internet 上,并通过 Internet 上也可用的软件服务进行访问和操作。云计算的定义特征是您使用的计算资源,如 CPU、内存、存储等,可以扩展和收缩以满足您的需要,并且您只为使用的资源付费。云计算既可以指软件服务本身,也可以指它们运行的​​硬件和软件环境。

Cloud computing is an old idea, but in recent years it has become ubiquitous. In cloud computing, information is stored on the Internet and accessed and manipulated through software services also available on the Internet. The defining characteristic of cloud computing is that the computing resources you use, such as CPU, memory, storage, and so on, can expand and contract to meet your need, and you pay only for what you use. Cloud computing can refer to both the software services themselves, and the hardware and software environments that they run on.

通常区分三类云计算 [9i7RMz]:云中的应用程序、云中的平台和云中的基础设施。云中的应用程序是 WordPress、SalesForce、Gmail 和维基百科等服务——托管在云基础设施上的基于 Web 的传统服务。SETI@Home 可能是最早的云应用主流示例。

It is common to distinguish three categories of cloud computing [9i7RMz]: applications in the cloud, platforms in the cloud, and infrastructure in the cloud. Applications in the cloud are services like WordPress, SalesForce, Gmail, and Wikipedia—traditional web-based services that are hosted on cloud infrastructure. SETI@Home was perhaps the earliest mainstream example of an application in the cloud.

云中的基础设施

Infrastructure in the Cloud

最高级别的可配置性是云中的基础设施,例如 Amazon Web Services (AWS)。AWS 提供许多基础设施服务,包括消息队列、静态内容托管、流视频托管、负载平衡和存储,此外还有其著名的名为 EC2 的实用程序 VM 托管服务。使用这些产品,您几乎可以完全控制系统,但您还必须完成大部分工作以将所有内容联系在一起。4个

At the highest level of configurability is infrastructure in the cloud, such as Amazon Web Services (AWS). AWS provides many infrastructural services, including message queues, static content hosting, streaming video hosting, load balancing, and storage, in addition to its well-known utility VM hosting service called EC2. With these offerings, you have almost complete control over the systems, but you also have to do most of the work to tie everything together.4

许多项目都将 AWS 用于其生产系统。假设您的软件架构正确,理想的是无共享架构,那么在基础设施方面扩展您的应用程序是相当简单的。您可以使用许多服务提供商来简化资源管理,以及构建在 AWS 之上的一系列令人眼花缭乱的专业服务和应用程序。但是,您使用这些服务的次数越多,您就越会被他们的专有架构所束缚。

Many projects are using AWS for their production systems. Assuming your software is architected properly, with the ideal being a shared-nothing architecture, scaling your application is fairly straightforward in terms of infrastructure. There are many providers of services which you can use to simplify the management of your resources, and a bewildering array of specialized services and applications built on top of AWS. However, the more you use these services, the more you lock yourself in to their proprietary architecture.

即使您不将 AWS 用于您的生产基础设施,它仍然可以成为您软件交付过程中非常有用的工具。EC2 可以轻松地按需启动新的测试环境。其他用途包括并行运行测试以加快测试速度、容量测试和多平台验收测试,如本章前面所述。

Even if you don’t use AWS for your production infrastructure, it can still be an extremely useful tool for your software delivery process. EC2 makes it easy to fire up new testing environments on demand. Other uses include running tests in parallel to speed them up, capacity testing, and multiplatform acceptance testing, as descibed earlier in this chapter.

迁移到云基础设施的人们提出了两个重要问题:安全性和服务水平。

There are two important issues that are raised by people migrating to cloud infrastructure: security and service levels.

安全往往是大中型公司提到的第一个拦路虎。将您的生产基础架构掌握在其他人手中,怎样才能阻止人们破坏您的服务或窃取您的数据?云计算提供商意识到了这个问题,并建立了各种机制来解决这些问题,例如高度可配置的防火墙和连接到您组织的 VPN 的专用网络。归根结底,虽然基于云的基础设施的风险不同,但您需要为基于云的部署制定计划,因此没有根本原因说明为什么基于云的服务不如托管在您拥有的基础设施上的公共可访问服务安全。

Security is often the first blocker mentioned by medium and large companies. With your production infrastructure in someone else’s hands, what’s to stop people compromising your service or stealing your data? Cloud computing providers are aware of this issue, and have established various mechanisms to address them, such as highly configurable firewalls and private networks which connect to your organization’s VPN. Ultimately, there is no fundamental reason why cloud-based services should be less secure than public-accessible services hosted on infrastructure that you own, although the risks with cloud-based infrastructure are different and you need to plan for a cloud-based rollout.

合规性经常被提及为使用云计算的限制条件。然而,问题通常不是法规禁止使用云计算,而是它们没有跟上它们的步伐。由于许多法规忽略了云计算,因此法规对云托管服务的影响有时没有得到充分理解,或者需要解释。考虑到仔细的规划和风险管理,通常可以调和两者。医疗保健公司 TC3 对其数据进行了加密,以便能够在 AWS 上托管其服务,从而能够保持 HIPAA 合规性。一些云供应商提供一定程度的 PCI DSS 合规性,有些还提供符合 PCI 标准的支付服务,因此您不必处理信用卡支付。

Compliance is often mentioned as a constraint on using cloud computing. However, the problem is usually not that regulations forbid the use of cloud computing so much as the fact they haven’t caught up with them. As many regulations ignore cloud computing, the implications of the regulations for cloudhosted services are sometimes not sufficiently well understood, or require interpretation. Given careful planning and risk management, it is usually possible to reconcile the two. The healthcare company TC3 encrypted its data in order to be able to host its service on AWS, and was thus able to remain HIPAA-compliant. Some cloud vendors provide a level of PCI DSS compliance, and some also provide payment services that are PCI-compliant so you don’t have to handle credit card payments. Even large organizations that require level 1 compliance can use a heterogeneous approach where the payment system is hosted in-house and the rest of the system is hosted in the cloud.

当您的整个基础架构外包时,服务水平尤为重要。至于安全性,您需要做一些研究以确保您的供应商能够满足您的要求。在性能方面尤其如此。亚马逊根据您的需求提供不同性能级别的服务,但即使是他们提供的最高级别也无法与高性能服务器相提并论。如果您需要运行具有大型数据集和高负载的 RDBMS,您可能不想将其置于虚拟化环境中。

Service levels are particularly important when your entire infrastructure is outsourced. As with security, you’ll need to do some research to ensure that your provider can meet your requirements. This is particularly the case when it comes to performance. Amazon offers services at different levels of performance depending upon your needs—but even the highest level they offer is no match for highperformance servers. If you need to run an RDBMS with a large dataset and a high load, you probably don’t want to put it in a virtualized environment.

云端平台

Platforms in the Cloud

云中平台的示例包括 Google App Engine 和 Force.com 等服务,其中服务提供商为您提供标准化的应用程序堆栈以供使用。作为您使用他们的堆栈的回报,他们会负责扩展应用程序和基础设施等事情。本质上,您牺牲了灵活性,以便提供商可以轻松处理非功能性需求,例如容量和可用性。云平台的优势如下:

Examples of platforms in the cloud include services, such as Google App Engine and Force.com, where the service provider gives you a standardized application stack to use. In return for your using their stack, they take care of things like scaling the application and infrastructure. Essentially, you sacrifice flexibility so that the provider can easily take care of nonfunctional requirements such as capacity and availability. The advantages of platforms in the cloud are the following:

• 在成本结构和配置灵活性方面,您可以获得与云中基础架构相同的所有优势。

• You get all the same benefits of infrastructure in the cloud in terms of cost structure and flexibility of provisioning.

• 服务提供商将负责非功能性需求,例如可伸缩性、可用性和(在某种程度上)安全性。

• The service provider will take care of nonfunctional requirements such as scalability, availability, and (to some extent) security.

• 您部署到一个完全标准化的堆栈,这意味着无需担心配置或维护测试、暂存或生产环境,也无需弄乱虚拟机映像。

• You deploy to a completely standardized stack, which means there is no need to worry about configuring or maintaining testing, staging, or production environments, and no messing around with virtual machine images.

最后一点尤其具有革命性。我们用本书的大部分篇幅讨论如何自动化部署、测试和发布过程以及如何设置和管理您的测试和部署环境。在云中使用平台几乎可以完全省去许多这些考虑因素。通常,您只需运行一条命令即可将您的应用程序部署到 Internet。您可以在几分钟内从无到有发布应用程序,而按钮式部署对您来说几乎是零投资。

The last point is especially revolutionary. We spend a good chunk of this book discussing how to automate your deploy, test, and release process and how to set up and manage your testing and deployment environments. Using platforms in the cloud almost completely dispenses with many of these considerations. Typically you can just run a single command to deploy your application to the Internet. You can go from nothing to a released application in literally minutes, and push-button deployments come with practically zero investment on your part.

云中平台的本质意味着您的应用程序将始终受到非常严格的限制。这就是允许这些服务提供简单部署和高可扩展性和性能的原因。例如,Google App Engine 只提供了 BigTable 的实现,而不是标准的 RDBMS。您不能启动新线程、调用 SMTP 服务器等。

The very nature of platforms in the cloud means that there will always be pretty severe constraints on your application. This is what allows these services to provide simple deployment and high scalability and performance. For example, Google App Engine only provides an implementation of BigTable, not a standard RDBMS. You can’t start new threads, call out to SMTP servers, and so forth.

云中的平台也会遇到同样的问题,这些问题可能会使云中的基础设施不适用。特别值得指出的是,对于云中的平台,对可移植性和供应商锁定的担忧要严重得多。

Platforms in the cloud also suffer from the same issues that can make infrastructure in the cloud unsuitable. In particular, it is worth pointing out that concerns over portability and vendor lock-in are considerably more severe with platforms in the cloud.

尽管如此,我们预计对于许多应用程序来说,这种类型的云计算将向前迈出一大步。事实上,我们希望这些类型的服务的可用性能够改变人们构建应用程序的方式。

Nevertheless, we expect that for many applications this type of cloud computing will be a large step forward. Indeed, we expect the availability of these types of services to change the way people architect applications.

一种尺寸不一定适合所有人

One Size Doesn’t Have to Fit All

当然,您可以混合搭配不同的服务来实施您的系统。例如,您可能在 AWS 上托管静态内容和流视频,在 Google App Engine 上托管您的应用程序,并在您自己的基础设施上运行专有服务。

Of course, you can mix and match different services to implement your system. For example, you might have static content and streaming video hosted on AWS, your application hosted on Google App Engine, and a proprietary service running on your own infrastructure.

为实现这一目标,应用程序必须设计为在这些异构环境中工作。这种部署要求您实现松散耦合的体系结构。异构解决方案在成本和满足非功能性需求的能力方面的价值为松耦合架构提供了一个引人注目的业务案例。实际上设计一个可行的方法很困难,超出了本书的范围。

To achieve this, applications have to be designed to work in these kinds of heterogeneous environments. This kind of deployment requires that you implement a loosely coupled architecture. The value of a heterogeneous solution in terms of cost and the ability to satisfy nonfunctional requirements makes a compelling business case for a loosely coupled architecture. Actually designing one that works is hard, and beyond the scope of this book.

云计算处于其发展的相对早期阶段。在我们看来,这不仅仅是一项最新的、被过度炒作的必备技术——它是真正向前迈出的一步,其重要性将在未来几年变得越来越重要。

Cloud computing is at a relatively early stage in its evolution. In our opinion, this is not just a latest overhyped, must-have technology—it is a genuine step forward that will grow in importance over the coming years.

对云计算的批评

Criticisms of Cloud Computing

尽管我们相信云计算将继续增长,但值得记住的是,并不是每个人都对亚马逊、IBM 或微软等公司向我们出售的云计算的巨大潜力感到高兴。

Although we are convinced cloud computing will continue to grow, it is worth bearing in mind that not everybody is overjoyed by the incredible potential of cloud computing as sold to us by the likes of Amazon, IBM, or Microsoft.

拉里埃里森特别评论说“关于云计算的有趣之处在于,我们已经重新定义了云计算以包括我们已经做的一切......除了改变云计算之外,我不明白我们会做些什么不同的事情我们一些广告的措辞。” (华尔街日报,2008 年 9 月 26 日)。他在理查德·斯托曼 (Richard Stallman) 身上找到了一个不太可能的盟友,后者甚至更加尖锐:“这是愚蠢的。这比愚蠢更糟糕:这是一场营销炒作。有人说这是不可避免的——每当你听到有人这么说时,很可能是一系列企业在为实现这一目标而努力。” (卫报,2008 年 9 月 29 日)。

Larry Ellison notably commented that “The interesting thing about cloud computing is that we’ve redefined cloud computing to include everything that we already do... I don’t understand what we would do differently in the light of cloud computing other than change the wording of some of our ads.” (Wall Street Journal, September 26, 2008). He found an unlikely ally in Richard Stallman, who was even more trenchant: “It’s stupidity. It’s worse than stupidity: it’s a marketing hype campaign. Somebody is saying this is inevitable—and whenever you hear somebody saying that, it’s very likely to be a set of businesses campaigning to make it true.” (The Guardian, September 29, 2008).

首先,“云”当然不是互联网——一个具有开放架构的系统,从头开始设计用于互操作性和弹性。每个供应商都提供不同的服务,您在某种程度上取决于您选择的平台。有一段时间,对等服务似乎是构建大型、分布式、可伸缩系统的最有可能的范例。然而,点对点的愿景还没有实现,也许是因为厂商很难从点对点服务中赚钱,而云计算仍然非常遵循效用计算模型,其收入特征是非常明白。从本质上讲,这意味着您的应用程序和数据最终受供应商支配。这可能是也可能不是对您当前基础架构的改进。

First of all, “the Cloud” is not, of course, the Internet—a system with an open architecture designed from the ground up for interoperability and resilience. Each vendor offers a different service, and you are to some extent tied to your choice of platform. For some time, peer-to-peer services seemed like the most likely paradigm for building large, distributed, scalable systems. However, the vision of peer-to-peer has not yet been realized, perhaps because it is so hard for vendors to make money from peer-to-peer services, and cloud computing still very much follows the utility computing model whose revenue characteristics are well understood. Essentially, that means that your application and your data are ultimately at the mercy of the vendors. This may or may not be an improvement over your current infrastructure.

目前,甚至对于效用计算服务使用的基本虚拟化平台也没有通用标准。API 级别的标准化似乎更不可能。桉树项目创建了一个实现部分 AWS API 以允许人们创建私有云,但 Azure 或 Google AppEngine 提供的 API 将很难重新实现。这使得应用程序难以移植。供应商锁定在云中和在其他地方一样或更多地成为现实。

At the moment, there is no common standard even for the basic virtualization platforms used by utility computing services. It seems even less likely that there will be standardization at the API level. The Eucalyptus project has created an implementation of parts of AWS’ API to allow people to create private clouds, but the APIs presented by Azure or Google AppEngine will be considerably harder to reimplement. This makes it hard to make applications portable. Vendor lock-in is as much, or more of, a reality in the cloud as it is elsewhere.

最后,根据您的应用程序,经济性可能会使使用云计算变得不合理。预测转向效用计算与拥有自己的基础设施相比的成本和节省,并运行概念验证来验证您的假设。考虑两种模型的盈亏平衡点等因素,同时考虑折旧、维护、灾难恢复、支持以及不在资本账户上支出的好处。云计算是否是适合您的模型取决于您的业务模型和组织限制以及技术问题。

Finally, depending on your application, the economics may simply make it unreasonable to use cloud computing. Project the costs and savings of moving to utility computing versus owning your own infrastructure, and run a proof of concept to validate your assumptions. Consider factors such as the break-even point of the two models, taking into account depreciation, maintenance, disaster recovery, support, and the benefits of not spending on the capital account. Whether or not cloud computing is the right model for you depends as much on your business model and organizational constraints as it does on technical concerns.

Armbrust 等人的论文“云之上:云计算的伯克利视角”[bTAJ0B] 中详细讨论了云计算的优缺点,包括一些有趣的经济模型。

There is a detailed discussion on the pros and cons of cloud computing, including some interesting economic modeling, in Armbrust et al.’s paper, “Above the Clouds: A Berkeley View of Cloud Computing” [bTAJ0B].

监控基础设施和应用程序

Monitoring Infrastructure and Applications

出于三个原因,深入了解生产环境中发生的事情至关重要。首先,如果企业拥有实时商业智能,例如他们产生了多少收入以及这些收入来自何处,他们可以更快地获得有关其战略的反馈。其次,当出现问题时,需要立即通知运营团队发生了事件,并拥有必要的工具来追踪事件的根本原因并进行修复。最后,历史数据对于规划目的至关重要。如果您没有有关系统在需求意外激增或添加新服务器时的行为方式的详细数据,就不可能计划改进您的基础架构以满足您的业务需求。

It is essential to have insight into what is going on in your production environments for three reasons. First, businesses can get feedback on their strategies much faster if they have real-time business intelligence, such as how much revenue they are generating and where that revenue is coming from. Second, when something goes wrong, the operations team needs to be informed immediately that there is an incident, and have the necessary tools to track down the root cause of the incident and fix it. Finally, historical data is essential for planning purposes. If you don’t have detailed data on how your systems behaved when there was an unexpected spike in demand, or when new servers were added, it’s impossible to plan evolving your infrastructure to meet your business requirements.

创建监控策略时需要考虑四个方面:

There are four areas to consider when creating a monitoring strategy:

• 检测您的应用程序和基础架构,以便您可以收集所需的数据

• Instrumenting your applications and your infrastructure so you can collect the data you need

• 存储数据以便轻松检索以进行分析

• Storing the data so it can easily be retrieved for analysis

• 创建仪表板以聚合数据并以适合运营和业务的格式呈现

• Creating dashboards which aggregate the data and present it in a format suitable for operations and for the business

• 设置通知以便人们可以了解他们关心的事件

• Setting up notifications so that people can find out about the events they care about

收集数据

Collecting Data

首先,决定要收集哪些数据很重要。监控数据可以来自以下来源:

First, it is important to decide what data you want to gather. Monitoring data can come from the following sources:

• 您的硬件,通过带外管理(也称为熄灯管理或 LOM)。几乎所有现代服务器硬件都实现了智能平台管理接口 (IPMI),它可以让您监控电压、温度、系统风扇速度、外围设备运行状况等,以及执行电源循环或点亮正面识别灯等操作面板,即使盒子断电。

• Your hardware, via out-of-band management (also known as lights-out management or LOM). Almost all modern server hardware implements the Intelligent Platform Management Interface (IPMI) which lets you monitor voltages, temperatures, system fan speeds, peripheral health, and so forth, as well as perform actions such as power cycling or lighting an identification light on the front panel, even if the box is powered off.

• 构成您的基础架构的服务器上的操作系统所有操作系统都提供接口来获取性能信息,例如内存使用情况、交换空间使用情况、磁盘空间、I/O 带宽(每个磁盘和 NIC)、CPU 使用情况等。监视进程表以计算出每个进程消耗的资源也很有用。在 UNIX 上,Collectd 是收集此数据的标准方法。在 Windows 上,它是使用称为性能计数器的系统完成的,其他性能数据提供者也可以使用该系统。

• The operating system on the servers comprising your infrastructure. All operating systems provide interfaces to get performance information such as memory usage, swap usage, disk space, I/O bandwidth (per disk and NIC), CPU usage, and so forth. It’s also useful to monitor the process table to work out the resources each process is consuming. On UNIX, Collectd is the standard way to gather this data. On Windows, it’s done using a system called performance counters, which can also be used by other providers of performance data.

• 您的中间件这可以提供有关内存、数据库连接池和线程池等资源使用情况的信息,以及连接数、响应时间等信息。

• Your middleware. This can provide information on the usage of resources such as memory, database connection pools, and thread pools, as well as information on the number of connections, response time, and so forth.

• 您的应用程序应编写应用程序,以便它们具有挂钩来监视操作和业务用户都关心的事情,例如业务交易的数量、它们的价值、转换率等。应用程序还应该使分析用户人口统计和行为变得容易。他们应该记录与他们所依赖的外部系统的连接状态。最后,他们应该能够报告其版本号和内部组件的版本(如果适用)。

• Your applications. Applications should be written so that they have hooks to monitor things that both operations and business users care about, such as the number of business transactions, their value, conversion rate, and so forth. Applications should also make it easy to analyze user demographics and behavior. They should record the status of connections to external systems that they rely on. Finally, they should be able to report their version number and the versions of their internal components, if applicable.

可以通过多种方式收集数据。首先,有许多工具——商业的和开源的——可以在整个数据中心收集上述所有内容,存储它,生成报告、图表和仪表板,并提供通知机制。领先的开源工具包括 Nagios、OpenNMS、Flapjack 和 Zenoss,尽管还有更多 [dcgsxa]。领先的商业参与者是 IBM 和 Tivoli、HP 和 Operations Manager、BMC 和 CA。该领域相对较新的商业进入者是 Splunk。

There are various ways data can be gathered. First of all, there are many tools—both commercial and open source—that will gather everything described above across your whole data center, store it, produce reports, graphs, and dashboards, and provide notification mechanisms. The leading open source tools include Nagios, OpenNMS, Flapjack, and Zenoss, although there are many more [dcgsxa]. The leading commercial players are IBM with Tivoli, HP with Operations Manager, BMC, and CA. A relatively new commercial entrant to the field is Splunk.

在引擎盖下,这些产品使用各种开放技术进行监控。主要的是 SNMP、它的后继者 CIM 和 JMX(用于 Java 系统)。

Under the hood, these products use various open technologies for monitoring. The main ones are SNMP, its successor CIM, and JMX (for Java systems).

图 11.9 SNMP 架构

Figure 11.9 SNMP architecture

图片

SNMP 是最受尊敬和普遍存在的监控标准。SNMP 具有三个主要组件:托管设备,它们是物理系统,例如服务器、交换机、防火墙等,与您要通过 SNMP 监控和管理的各个应用程序或设备对话的代理,以及网络管理系统监视和控制受管设备。网络管理系统和代理通过 SNMP 网络协议进行通信,SNMP 网络协议是一种位于标准 TCP/IP 堆栈之上的应用层协议。SNMP 的体系结构如图 11.9所示。

SNMP is the most venerable and ubiquitous standard for monitoring. SNMP has three main components: managed devices, which are physical systems such as servers, switches, firewalls, and so forth, agents that talk to the individual applications or devices that you want to monitor and manage via SNMP, and a network management system which monitors and controls managed devices. Network management systems and agents communicate via the SNMP network protocol, which is an application-layer protocol that sits on top of the standard TCP/IP stack. SNMP’s architecture is shown in Figure 11.9.

在 SNMP 中,一切都是变量。您通过观察变量来监视系统并通过设置变量来控制它们。哪些变量可用于任何给定类型的 SNMP 代理、它们的描述、类型以及它们是否可以写入或只读,在 MIB(管理信息库)(一种可扩展的数据库格式)中进行了描述。每个供应商为其提供 SNMP 代理的系统定义 MIB,并且 IANA 维护一个中央注册表 [aMiYLA]。几乎每个操作系统、最常见的中间件(例如 Apache、WebLogic 和 Oracle)以及许多设备都内置了 SNMP。当然,您也可以为自己的应用程序创建 SNMP 代理和 MIB,尽管这是一项需要开发和运营团队之间密切协作的艰巨任务。

In SNMP, everything is a variable. You monitor systems by watching variables and control them by setting variables. Which variables are available for any given type of SNMP agent, with their descriptions, their types, and whether they can be written to or are read-only, is described in a MIB (Management Information Base), an extensible database format. Each vendor defines MIBs for the systems it provides SNMP agents for, and the IANA maintains a central registry [aMiYLA]. Pretty much every operating system, most common middleware (Apache, WebLogic, and Oracle, for example), as well as many devices have SNMP built-in. Of course, you can also create SNMP agents and MIBs for your own applications, although it’s a nontrivial undertaking that will require close collaboration between the development and operations teams.

记录

Logging

日志记录还必须构成监控策略的核心部分。操作系统和中间件生成的日志对于了解用户行为和追踪问题根源都非常有用。

Logging also has to form a central part of your monitoring strategy. Operating systems and middleware produce logs which are tremendously useful both for understanding user behavior and for tracking down the source of problems.

您的应用程序还需要生成高质量的日志。尤其要注意日志级别。大多数日志记录系统都有多个级别,例如DEBUGINFOWARNINGERRORFATAL默认情况下,您的应用程序应该只显示WARNING -、ERROR - 和FATAL -级别的消息,但在运行时或部署时可配置以在需要调试时显示其他级别。由于日志仅供运营团队使用,因此可以在日志消息中打印底层异常。这可以显着帮助调试过程。

Your applications also need to produce good quality logs. In particular, it’s important to pay attention to log levels. Most logging systems have several levels, such as DEBUG, INFO, WARNING, ERROR, and FATAL. By default, your application should only show WARNING-, ERROR-, and FATAL-level messages, but be configurable at run time or deploy time to show other levels when debugging is necessary. Since logs are only available to operations teams, it’s acceptable to print underlying exceptions in log messages. This can significantly help the debugging process.

请记住,运营团队是日志文件的主要消费者。对于开发人员来说,花时间与支持解决用户报告的问题或与解决生产中的问题的操作一起工作是有益的。开发人员将很快了解到可恢复的应用程序错误(例如用户无法登录)不应该属于DEBUG级别以上的任何地方,而应用程序所依赖的外部系统的超时应该处于ERRORFATAL级别(取决于您的应用程序仍然可以在没有外部服务的情况下处理交易)。

Bear in mind that the operations team is the main consumer of log files. It’s instructive for developers to spend time working with support solving problems reported by users, or with operations solving problems in production. Developers will quickly learn that recoverable application errors, such as a user being unable to log in, should not belong anywhere above DEBUG level, whereas a timeout on an external system your application depends on should be at ERROR or FATAL level (depending on whether your application can still process transactions without the external service).

日志记录是可审计性的一部分,应被视为第一级需求集,与任何其他非功能性需求一样。与您的运营团队交谈,确定他们需要什么,并从一开始就将这些要求纳入其中。特别要考虑日志全面性和人类可读性之间的权衡。人们必须能够通过日志文件分页或轻松地对其进行 grep 以获取他们想要的数据——这意味着每个条目都应该使用表格或基于列的格式的单行,使时间戳一目了然,日志级别,错误来自应用程序中的位置,以及错误代码和描述。

Logging, which is part of auditability, should be treated as a first-level set of requirements, the same as any other nonfunctional requirements. Talk to your operations team to work out what they need, and build these requirements in from the beginning. Consider, in particular, the tradeoff between logs being comprehensive and human-readable. It’s essential for humans to be able to either page through a log file or grep it easily to get the data they want—which means that each entry should use a single line in a tabular or column-based format that exposes at a glance the timestamp, the log level, where in the application the error came from, and the error code and description.

创建仪表板

Creating Dashboards

图 11.10 Nagios 截图

Figure 11.10 Nagios screenshot

图片

与开发团队的持续集成一样,运营团队必须有一个大的可见显示器,如果有任何事件发生,他们可以在高层次上看到。然后,当出现问题时,他们需要能够深入细节以找出问题所在。所有开源和商业工具都提供这种功能,包括查看历史趋势和进行某种报告的能力。Nagios 的屏幕截图如图 11.10所示。了解每个应用程序的哪个版本在哪个环境中也非常有用,这将需要一些额外的检测和集成工作。

As with continuous integration for the development team, it’s essential that the operations team has a big visible display where they can see at a high level if there are any incidents. They then need to be able to dive into the detail when things go wrong to work out what the problem is. All the open source and commercial tools offer this kind of facility, including the ability to view historical trends and do some kind of reporting. A screenshot from Nagios is shown in Figure 11.10. It’s also extremely useful to know which version of each application is in which environment, and that will require some additional instrumentation and integration work.

您可能会监控成千上万的事情,提前计划非常重要,这样您的操作仪表板就不会淹没在噪音中。拿出一份风险清单,按概率和影响分类。您的列表可能包括一般风险,例如磁盘空间不足或未经授权访问您的环境,以及您的业务的特定风险,例如无法完成的交易。然后,您需要确定实际监控的内容,以及如何显示该信息。

There are potentially thousands of things that you could monitor, and it is essential to plan ahead so your operations dashboard isn’t drowned in noise. Come up with a list of risks, categorized by probability and impact. Your list might include generic risks, like running out of disk space or unauthorized access to your environments, as well as specific risks to your business, such as transactions that couldn’t be completed. You then need to work out what to actually monitor, and how to display that information.

在聚合数据方面,红色-琥珀色-绿色交通灯聚合很好理解和常用。首先,您需要确定要聚合到哪些实体。您可以为环境、应用程序或业务功能创建交通信号灯。不同的实体将适合不同的目标受众。完成此操作后,您需要为红绿灯设置阈值。Nygard 提供了以下指南(Nygard,2007 年,第 273 页)。

In terms of aggregating data, the red-amber-green traffic light aggregation is well understood and commonly used. First of all, you need to work out which entities to aggregate up to. You could create traffic lights for environments, for applications, or for business functions. Different entities will be appropriate for different target audiences. Once you’ve done this, you need to set thresholds for the traffic lights. Nygard provides the following guidelines (Nygard, 2007, p. 273).

绿色表示以下所有情况都为真:

Green means all of the following are true:

• 所有预期的事件都已发生。

• All expected events have occurred.

• 没有发生异常事件。

• No abnormal events have occurred.

• 所有指标都是名义上的(在这个时间段的两个标准偏差内)。

• All metrics are nominal (within two standard deviations for this time period).

• 所有州都全面运作。

• All states are fully operational.

琥珀色表示以下至少一项为真:

Amber means at least one of the following is true:

• 预期的事件没有发生。

• An expected event has not occurred.

• 至少发生了一个中等严重程度的异常事件。

• At least one abnormal event, with a medium severity, has occurred.

• 一个或多个参数高于或低于标称值。

• One or more parameters are above or below the nominal values.

• 非关键状态未完全运行(例如,断路器切断了非关键功能)。

• A noncritical state is not fully operational (for example, a circuit breaker has cut off a noncritical feature).

红色表示以下至少一项为真:

Red means at least one of the following is true:

• 所需的事件没有发生。

• A required event has not occurred.

• 至少发生了一个具有高严重性的异常事件。

• At least one abnormal event, with a high severity, has occurred.

• 一个或多个参数远高于或低于标称值。

• One or more parameters are far above or below the nominal values.

• 临界状态未完全运行(例如,“接受请求”在本应为真时却为假)。

• A critical state is not fully operational (for example, “accepting requests” is false where it should be true).

行为驱动监控

Behavior-Driven Monitoring

与开发人员通过编写自动化测试来验证其应用程序的行为来执行行为驱动开发的方式相同,运维人员也可以编写自动化测试来验证其基础架构的行为。您可以从编写测试开始,验证它是否失败,然后定义一个 Puppet 清单(或您选择的任何配置管理工具),使您的基础设施进入预期状态。然后您运行测试以验证配置是否正常工作以及您的基础架构是否按预期运行。

In the same way that developers perform behavior-driven development by writing automated tests to verify the behavior of their applications, operations personnel can write automated tests to verify the behavior of their infrastructure. You can start by writing the test, verifying that it fails, and then defining a Puppet manifest (or whatever your configuration management tool of choice is) that puts your infrastructure into the expected state. You then run the test to verify that the configuration worked correctly and your infrastructure behaves as expected.

提出这个想法的 Martin Englund 使用 Cucumber 编写测试。这是他的博客条目 [cs9LsY] 中的示例:

Martin Englund, who came up with the idea, uses Cucumber to write tests. Here’s an example from his blog entry [cs9LsY]:

图片

Lindsay Holmwood 编写了一个名为 Cucumber-Nagios [anKH1W] 的程序,它允许您编写输出 Nagios 插件预期格式的 Cucumber 测试,这样您就可以在 Cucumber 中编写 BDD 风格的测试并在 Nagios 中监控结果。

Lindsay Holmwood wrote a program called Cucumber-Nagios [anKH1W] which allows you to write Cucumber tests that output the format expected of Nagios plugluginins, so that you can write BDD-style tests in Cucumber and monitor the results in Nagios.

您还可以使用此范例将应用程序的冒烟测试插入到监控应用程序中。只需选择您的应用程序的冒烟测试并将它们插入带有 Cucumber-Nagios 的 Nagios,您不仅可以验证您的 Web 服务器是否正常运行,而且您的应用程序实际上是否按预期工作。

You can also use this paradigm to plug smoke tests for your applications into your monitoring application. Simply take a selection of your application’s smoke tests and plug them into Nagios with Cucumber-Nagios, and you can verify not just that your web server is up, but that your application is in fact working as expected.

概括

Summary

如果您在阅读本章后觉得我们做得太过分了,我们可以理解——我们是认真地建议您的基础设施应该完全自主吗?我们真的认为您应该尝试颠覆使用您昂贵的企业软件提供的管理工具吗?好吧,实际上,是的;我们是在我们认为合理的范围内提出这些建议。

We can understand if, having read this chapter, you feel that we are taking things too far—are we seriously suggesting that your infrastructure should be completely autonomic? Do we really think that you should try to subvert the use of the administration tools supplied with your expensive enterprise software? Well, actually, yes; we are suggesting those things, within what we consider to be reasonable limits.

正如我们所说,您需要对基础架构进行配置管理的程度取决于其性质。一个简单的命令行工具可能对其运行环境没有什么期望,而一级网站将需要考虑所有这些甚至更多。根据我们的经验,大多数企业应用程序应该比他们更认真地考虑配置管理,如果他们没有这样做,就会导致许多延迟、开发效率损失以及持续增加的拥有成本。

As we have said, the degree to which you need to take the configuration management of your infrastructure will depend on its nature. A simple commandline tool may have few expectations of the environment in which it runs, whereas a tier 1 website will need to consider all of these things and more. In our experience, most enterprise applications should consider configuration management much more seriously than they do, and their failure to do so results in many delays, losses of efficiency in development, and increased cost of ownership on an ongoing basis.

我们在本章中提出的建议和我们描述的策略肯定会增加您必须创建的部署系统的复杂性。他们可能会要求您提出创造性的解决方法,以解决第三方产品中对配置管理支持不佳的问题。但是,如果您正在创建一个具有许多配置点的大型复杂系统,并且可能依赖于许多技术,那么这种方法可以节省您的项目。

The recommendations we have made and the strategies we have described in this chapter certainly add complexity to the deployment systems that you must create. They may challenge you to come up with creative workarounds for the poor support for configuration management in your third-party products. But if you are creating a large and complex system with many configuration points, and perhaps relying on many technologies, this approach can save your project.

如果它既便宜又容易实现,我们都会想要自主的基础设施,从而可以直接创建我们生产环境的副本。这个事实是如此明显,以至于几乎不值得一提。然而,如果它是免费的,我们都会接受它,那么我们对能够随时完美地重现任何环境的唯一反对意见就是成本。因此,在成本范围内,介于免费和过于昂贵之间的某个地方是值得承担的成本。

If it were cheap and easy to accomplish, we would all want autonomic infrastructure, making it straightforward to create copies of our production environments. This fact is so obvious that it is almost not worth stating. However, if we would all take it if it was free, then our only objection to having the ability to perfectly reproduce any environment at any time is that of cost. So, somewhere on the spectrum of costs, between free and too expensive, is a cost that is worth bearing.

我们相信,使用本章中描述的技术以及更广泛的部署管道战略选择,您可以在一定程度上管理这些成本。虽然无疑会增加创建版本控制、构建和部署系统的成本,但这些成本远远超过了手动环境管理的成本,不仅在应用程序的整个生命周期,甚至在初始开发阶段。

We believe that using the techniques described in this chapter, as well as the broader strategic choice of the deployment pipeline, you can manage these costs to a degree. While undoubtedly adding something to the cost of creating your version control, build, and deployment systems, these costs are dramatically outweighed by the costs of manual environment management not only across the lifecycle of your applications, but even across the initial development phase.

如果您正在评估在您的企业系统中使用的第三方产品,确保它们适合您的自动化配置管理策略应该是您的列表或优先事项中非常重要的。哦,如果他们的产品在这方面缺乏,请帮我们大家一个忙,让任何此类产品的供应商都很难过。太多的人在支持严肃的配置管理时草率和三心二意。

If you are evaluating third-party products for use in your enterprise system, making sure that they fit into your automated configuration management strategy should be very high on your list or priorities. Oh, and please do us all a favor and give any vendors of such products a hard time if their product is lacking in this regard. Too many are sloppy and half-hearted in their support of serious configuration management.

最后,确保您从项目一开始就制定了基础架构管理策略,并在该阶段让开发和运营团队的利益相关者参与进来。

Finally, make sure that you have an infrastructure management strategy in place right from the beginning of your project, and engage stakeholders from the development and operations teams at that stage.

第 12 章管理数据

Chapter 12. Managing Data

介绍

Introduction

出于两个原因,数据及其管理和组织为测试和部署过程带来了一组特定的问题。首先,通常涉及的信息量巨大。分配给我们应用程序行为编码的字节——它的源代码和配置信息——通常远远超过记录其状态的数据量。其次是应用程序数据的生命周期不同于系统其他部分的事实。应用程序数据需要保存——事实上,数据通常比用于创建和访问它的应用程序更持久。至关重要的是,在系统的新部署或回滚期间需要保留和迁移数据。

Data and its management and organization pose a particular set of problems for testing and deployment processes for two reasons. First, there is the sheer volume of information that is generally involved. The bytes allocated to encoding the behavior of our application—its source code and configuration information—are usually vastly outweighed by the volume of data recording its state. Second is the fact that the lifecycle of application data differs from that of other parts of the system. Application data needs to be preserved—indeed, data usually outlasts the applications that were used to create and access it. Crucially, data needs to be preserved and migrated during new deployments or rollbacks of a system.

在大多数情况下,当我们部署新代码时,我们可以擦除以前的版本并用新的副本完全替换它。这样我们就可以确定我们的起始位置。虽然该选项在少数有限情况下适用于数据,但对于大多数现实世界的系统而言,这种方法是不可能的。一旦系统投入生产,与之相关的数据将会增长,并且它本身将具有重要的价值。事实上,可以说它是您系统中最有价值的部分。当我们需要修改结构或内容时,这就会出现问题。

In most cases, when we deploy new code, we can erase the previous version and wholly replace it with a new copy. In this way we can be certain of our starting position. While that option is possible for data in a few limited cases, for most real-world systems this approach is impossible. Once a system has been released into production, the data associated with it will grow, and it will have significant value in its own right. Indeed, arguably it is the most valuable part of your system. This presents problems when we need to modify either the structure or the content.

随着系统的成长和发展,不可避免地需要进行此类修改,因此我们必须建立允许完成更改的机制,同时最大限度地减少中断并最大限度地提高应用程序和部署过程的可靠性。这样做的关键是自动化数据库迁移过程。现在有许多工具可以使数据迁移自动化相对简单,因此可以将其编写为自动化部署过程的一部分。这些工具还允许您对数据库进行版本控制并将其从任何版本迁移到任何其他版本。这具有将开发过程与部署过程分离的积极效果——您可以为所需的每个数据库更改创建迁移,即使您没有独立部署每个架构更改。这也意味着您的数据库管理员 (DBA) 不需要大的前期计划——他们可以随着应用程序的发展逐步工作。

As systems grow and evolve, it is inevitable that such modifications will be required, so we must put mechanisms into place that allow changes to be accomplished while minimizing disruption and maximizing the reliability of the application and of the deployment process. The key to this is automating the database migration process. A number of tools now exist that make automating of data migration relatively straightforward, so that it can be scripted as part of your automated deployment process. These tools also allow you to version your database and migrate it from any version to any other. This has the positive effect of decoupling the development process from the deployment process—you can create a migration for each database change required, even if you don’t deploy every schema change independently. It also means that your database administrators (DBAs) don’t need a big up-front plan—they can work incrementally as the application evolves.

本章涉及的另一个重要领域是测试数据的管理。在执行验收测试或容量测试(有时甚至是单元测试)时,许多团队的默认选项是转储生产数据。由于许多原因(尤其是数据集的大小),这是有问题的,我们在这里提供了替代策略。

The other important area we cover in this chapter is the management of test data. When performing acceptance testing or capacity testing (or even, sometimes, unit testing), the default option for many teams is to take a dump of the production data. This is problematic for many reasons (not least the size of the dataset), and we provide alternative strategies here.

本章其余部分的一个警告:绝大多数应用程序都依赖关系数据库技术来管理它们的数据。这不是存储数据的唯一方式,当然也不是所有用途的最佳选择,正如 NoSQL 运动的兴起所证明的那样。我们在本章中提供的建议与任何数据存储系统都相关,但在讨论细节时,我们将讨论 RDBMS 系统,因为它们仍然代表了应用程序的绝大多数存储系统。

One caveat for the rest of this chapter: The vast majority of applications rely on relational database technology to manage their data. This isn’t the only way of storing data, and is certainly not the best choice for all uses, as the rise of the NoSQL movement demonstrates. The advice that we offer in this chapter is relevant to any system of data storage, but where we discuss details, we will be talking about RDBMS systems, since they still represent the vast majority of storage systems for applications.

数据库脚本

Database Scripting

与系统的任何其他更改一样,在构建、部署、测试和发布过程中使用的任何数据库的任何更改都应通过自动化流程进行管理。这意味着数据库初始化和所有迁移都需要作为脚本捕获并检查到版本控制中。应该可以使用这些脚本来管理交付过程中使用的每个数据库,无论是为处理代码的开发人员创建新的本地数据库,为测试人员升级系统集成测试 (SIT) 环境,还是迁移生产数据库作为发布过程的一部分。

As with any other change to your system, any changes to any databases used as part of your build, deploy, test, and release process should be managed through automated processes. That means that database initialization and all migrations need to be captured as scripts and checked into version control. It should be possible to use these scripts to manage every database used in your delivery process, whether it is to create a new local database for a developer working on the code, to upgrade a systems integration testing (SIT) environment for testers, or to migrate production databases as part of the release process.

当然,您的数据库架构将随着您的应用程序而发展。这带来了一个问题,因为数据库具有正确的模式以与您的应用程序的特定版本一起工作是很重要的。例如,部署到登台时,必须能够将登台数据库迁移到正确的模式以与正在部署的应用程序版本一起使用。如第 327 页的“增量更改”部分所述,仔细管理脚本可以实现这一点

Of course, the schema of your database will evolve along with your application. This presents a problem because it is important that the database has the correct schema to work with a particular version of your application. For example, when deploying to staging, it is essential to be able to migrate the staging database to the correct schema to work with the version of the application being deployed. Careful management of your scripts makes this possible, as described in the “Incremental Change” section on page 327.

最后,您的数据库脚本也应该用作持续集成过程的一部分。虽然根据定义,单元测试不应该需要数据库才能运行,但是针对使用数据库的应用程序运行的任何类型的有意义的验收测试都需要正确初始化数据库。因此,您的验收测试设置过程的一部分应该是创建一个具有正确模式的数据库,以使用最新版本的应用程序,并加载运行验收测试所需的任何测试数据。类似的过程可用于部署管道的后续阶段。

Finally, your database scripts should also be used as part of your continuous integration process. While unit tests should not, by definition, require a database in order to run, any kind of meaningful acceptance tests running against a database-using application will require the database to be correctly initialized. Thus, part of your acceptance test setup process should be creating a database with the correct schema to work with the latest version of the application and loading it with any test data necessary to run the acceptance tests. A similar procedure can be used for later stages in the deployment pipeline.

初始化数据库

Initializing Databases

我们交付方法的一个极其重要的方面是能够以自动化方式重现环境以及在其中运行的应用程序。没有这种能力,我们就无法确定系统会按照我们预期的方式运行。

An extremely important aspect of our approach to delivery is the ability to reproduce an environment, along with the application running in it, in an automated fashion. Without this ability, we can’t be certain that the system will behave in the way we expect.

随着应用程序在开发过程中发生变化,数据库部署的这一方面是最容易正确和维护的。几乎每个数据管理系统都支持从自动化脚本初始化数据存储的能力,包括模式和用户凭证。因此,创建和维护数据库初始化脚本是一个简单的起点。您的脚本应该首先创建数据库的结构、数据库实例、模式等,然后使用您的应用程序启动所需的任何参考数据填充数据库中的表。

This aspect of database deployment is the simplest to get right and to maintain as your application changes through the development process. Almost every data management system supports the ability to initialize a data store, including schemas and user credentials, from automated scripts. So, creating and maintaining a database initialization script is a simple starting point. Your script should first create the structure of the database, database instances, schemas, and so on, and then populate the tables in the database with any reference data required for your application to start.

该脚本以及维护数据库所涉及的所有其他脚本当然应该与您的代码一起存储在版本控制中。

This script, along with all other scripts involved in maintaining the database, should of course be stored in version control along with your code.

对于一些简单的项目,这就足够了。对于操作数据集在某种程度上是瞬态的项目——或者它是预定义的,例如在运行时使用数据库作为只读资源的系统——简单地擦除以前的版本并用一个新的副本替换它,重新-从版本化存储创建,是一种简单有效的策略。如果你能摆脱它,就这样做吧!

For a few simple projects, this can be enough. For projects where the operational dataset is in some manner transient—or where it is predefined, such as systems that use a database as a read-only resource at run time—simply erasing the previous version and replacing it with a fresh new copy, re-created from versioned storage, is a simple and effective strategy. If you can get away with it, do this!

那么,最简​​单的重新部署数据库的过程如下:

At its simplest, then, the process for deploying a database afresh is as follows:

• 擦除之前的内容。

• Erase what was there before.

• 创建数据库结构、数据库实例、模式等。

• Create the database structure, database instances, schemas, etc.

• 用数据加载数据库。

• Load the database with data.

大多数项目以比这更复杂的方式使用数据库。我们需要考虑更复杂但更常见的情况,即我们在使用一段时间后进行更改。在这种情况下,现有数据必须作为部署过程的一部分进行迁移。

Most projects use databases in more sophisticated ways than this. We will need to consider the more complex, but more common, case where we are making a change after a period of use. In this case, there is existing data that has to be migrated as part of the deployment process.

增量变化

Incremental Change

持续集成要求我们能够在对应用程序进行每次更改后保持应用程序正常运行。这包括对我们数据的结构或内容的更改。持续交付要求我们必须能够将应用程序的任何成功发布候选版本(包括对数据库的更改)部署到生产环境中(对于包含数据库的用户安装软件也是如此)。对于除了最简单的系统之外的所有系统,这意味着必须更新操作数据库,同时保留其中保存的有价值数据。最后由于数据库中的数据必须保留的约束在部署期间,如果部署由于某种原因出错,我们需要有一个回滚策略。

Continuous integration demands that we are able to keep the application working after every change made to it. This includes changes to the structure or content of our data. Continuous delivery demands that we must be able to deploy any successful release candidate of our application, including the changes to the database, into production (the same is also true for user-installed software that contains a database). For all but the simplest of systems, that means having to update an operational database while retaining the valuable data that is held in it. Finally, due to the constraint that the data in the database must be retained during a deployment, we need to have a rollback strategy should a deployment go wrong for some reason.

对数据库进行版本控制

Versioning Your Database

以自动化方式迁移数据的最有效机制是对数据库进行版本控制。只需在您的数据库中创建一个包含其版本号的表。然后,每次对数据库进行更改时,都需要创建两个脚本:一个将数据库从版本x升级到版本x +1(前滚脚本),另一个将数据库从版本x +升级1 到版本x(回滚脚本)。您还需要为您的应用程序配置一个配置设置,指定它要使用的数据库版本(这可以在版本控制中保持为常量,并在每次需要更改数据库时更新)。

The most effective mechanism to migrate data in an automated fashion is to version your database. Simply create a table in your database that contains its version number. Then, every time you make a change to the database, you need to create two scripts: one that takes the database from a version x to version x + 1 (a roll-forward script), and one that takes it from version x + 1 to version x (a roll-back script). You will also need to have a configuration setting for your application specifying the version of the database it is designed to work with (this can be kept as a constant in version control and updated every time a database change is required).

在部署时,您可以使用一种工具来查看当前部署的数据库版本以及正在部署的应用程序版本所需的数据库版本。然后该工具将确定要运行哪些脚本以将数据库从当前版本迁移到所需版本,并按顺序在数据库上运行它们。对于前滚,它将应用前滚脚本的正确组合,从最旧到最新;对于回滚,它将以相反的顺序应用相关的回滚脚本。如果您使用 Ruby on Rails,则此技术已经以 ActiveRecord 迁移的形式内置。如果您使用的是 Java 或 .NET,我们的一些同事开发了一个名为 DbDeploy(.NET 版本为 DbDeploy.NET)的简单开源应用程序来为您管理这个过程。

At deployment time, you can then use a tool which looks at the version of the database currently deployed and the version of the database required by the version of the application that is being deployed. The tool will then work out which scripts to run to migrate the database from its current version to the required version, and run them on the database in order. For a roll forward, it will apply the correct combination of roll-forward scripts, from oldest to newest; for a roll back, it will apply the relevant roll-back scripts in reverse order. This technique is already built in if you’re using Ruby on Rails, in the form of ActiveRecord migrations. If you’re using Java or .NET, some of our colleagues developed a simple open source application called DbDeploy (the .NET version is DbDeploy.NET) to manage this process for you. There are also several other solutions that do similar things, including Tarantino, Microsoft’s DbDiff, and IBatis’ Dbmigrate.

这是一个简单的例子。当您第一次开始编写应用程序时,您会编写第一个 SQL 文件 1_create_initial_tables.sql:

Here’s a simple example. When you first start writing your application, you write your first SQL file, 1_create_initial_tables.sql:

图片

在更高版本的代码中,您发现需要将客户的出生日期添加到表中。因此,您创建了另一个脚本 2_add_customer_date_of_birth.sql,它描述了如何添加此更改以及如何将其回滚:

In a later version of your code, you discover you need to add the customer’s date of birth to the table. So you create another script, 2_add_customer_date_of_birth.sql, that describes how to add this change and how to roll it back:

图片

--//@UNDO注释之前的文件部分表示如何从数据库的版本 1 前滚到版本 2。注释后面的文件部分表示如何从版本 2 回滚到版本 1。此语法是 DbDeploy 和 DbDeploy.NET 使用的语法。

The part of the file before the --//@UNDO comment represents how to roll forward from version 1 to version 2 of the database. The part of the file after the comment represents how to roll back from version 2 to version 1. This syntax is the one used by DbDeploy and DbDeploy.NET.

如果您的前滚脚本向您的数据库添加新结构,那么编写回滚脚本并不难。您的回滚脚本可以简单地删除它们,记住首先删除任何引用约束。通常也可以为更改现有结构的更改创建相应的回滚脚本。但是,在某些情况下,有必要删除数据。在这种情况下,仍然可以使前滚脚本无损。让您的脚本创建一个临时表,在从主表中删除数据之前,将要销毁的数据复制到该临时表中。执行此操作时,还必须复制表的主键,以便可以通过回滚脚本复制回数据并重新建立引用约束。

Writing roll-back scripts isn’t too hard if your roll-forward scripts add new structures to your database. Your roll-back scripts can simply delete them, remembering to remove any referential constraints first. It is usually also possible to create corresponding roll-back scripts for changes that alter existing structures. However, in some cases it is necessary to delete data. In this situation, it is still possible to make your roll-forward script nondestructive. Have your script create a temporary table into which the data to be destroyed is copied before it is deleted from the main table. It is essential, when doing this, to also copy over the table’s primary keys so that the data can be copied back, and the referential constraints reestablished, by the roll-back script.

有时,您可以轻松地前后移动数据库的程度存在实际限制。根据我们的经验,导致困难的最常见问题是更改数据库模式。如果此类更改是累加的,因为它们会创建新的关系,那么您基本上没什么问题——除非您执行某些操作,例如添加现有数据违反的约束,或添加没有默认值的新对象。如果模式更改是减法的,就会出现问题,因为一旦您丢失了一条记录与另一条记录的相关信息,就很难再次重建这种关系。

There are sometimes practical limits to the degree to which you can easily step databases back and forward. In our experience, the commonest problem that causes difficulty is changing the database schema. If such changes are additive, in that they create new relationships, you are mostly fine—unless you do things like adding constraints that existing data violates, or adding new objects without default values. If schema changes are subtractive, problems arise because once you have lost information on how one record is related to another, it is harder to reconstitute that relationship again.

管理数据库更改的技术实现了两个目标:首先,它允许您持续部署您的应用程序,而不必担心您要部署到的环境中数据库的当前状态。您的部署脚本只是简单地将数据库回滚到您的应用程序期望的版本。

The technique of managing database changes achieves two goals: First, it allows you to continuously deploy your application without worrying about the current state of the database in the environment you’re deploying to. Your deployment script simply rolls the database back or forward to the version your application is expecting.

但是,它还允许您在某种程度上将对数据库的更改与对应用程序的更改分离开来。您的 DBA 可以使用脚本来迁移您的数据库并将它们签入版本控制,而不必担心它们可能会破坏您的应用程序。为实现这一点,您的 DBA 只需确保他们是迁移到更新版本数据库的一部分,在编写代码以使用它并且开发人员将所需的数据库版本设置为更新版本之前,该版本不会实际运行.

However, it also allows you to decouple, to some extent, changes to the database from changes to the application. Your DBA can work on scripts to migrate your database and check them into version control without having to worry that they might break your application. To achieve this, your DBA simply ensures they are part of a migration to a newer version of the database, which won’t actually run until the code is written to use it and the developers set the version of the database required to the newer version.

我们推荐 Scott Ambler 和 Pramod Sadalage 的优秀著作Refactoring Databases,以及随附的迷你书Recipes for Continuous Database Integration,以获取有关管理数据库增量更改的更多详细信息。

We recommend Scott Ambler and Pramod Sadalage’s excellent book Refactoring Databases, and the accompanying minibook Recipes for Continuous Database Integration, for more detail on managing incremental changes to databases.

管理精心策划的变化

Managing Orchestrated Changes

在许多组织中,通过单个数据库集成所有应用程序是很常见的。这不是我们推荐的做法;最好让应用程序直接相互对话,并在必要时分解出公共服务(例如,在面向服务的体系结构中)。但是,有些情况在通过数据库集成是有意义的,或者更改应用程序的体系结构的工作量太大。

In many organizations, it is common to integrate all applications through a single database. This is not a practice we recommend; it’s better to have applications talk to each other directly and factor out common services where necessary (as, for example, in a service-oriented architecture). However, there are situations in which it either makes sense to integrate via the database, or it is simply too much work to change your application’s architecture.

在这种情况下,对数据库进行更改会对使用该数据库的其他应用程序产生连锁反应。首先,重要的是在编排环境中测试此类更改——换句话说,在数据库合理地类似于生产的环境中,并且托管使用它的其他应用程序的版本。这样的环境通常被称为系统集成测试 (SIT) 环境,或者备选地暂存。这样,假设经常针对使用该数据库的其他应用程序运行测试,您很快就会发现您是否影响了另一个应用程序。

In this case, making a change to a database can have a knock-on effect on other applications that use the database. First of all, it is important to test such changes in an orchestrated environment—in other words, in an environment in which the database is reasonably production-like, and which hosts versions of the other applications that use it. Such an environment is often known as a systems integration testing (SIT) environment, or alternatively staging. In this way, assuming tests are frequently run against the other applications that use the database, you will soon discover if you have affected another application.

在这样的环境中,保留哪些应用程序使用哪些数据库对象的注册表也很有用,这样您就知道哪些更改会影响哪些其他应用程序。

In such environments, it is also useful to keep a registry of which applications use which database objects, so you know which changes will affect which other applications.


图片

我们看到使用的一种方法是通过代码库的静态分析自动生成每个应用程序接触的数据库对象列表。此列表是作为每个应用程序构建过程的一部分生成的,结果可供其他人使用,因此很容易确定您是否要影响其他人的应用程序。

One approach we have seen used is to autogenerate a list of database objects touched by each application through static analysis of the codebase. This list is generated as part of the build process for every application, and the results are made available to everybody else, so it is easy to work out if you are going to affect somebody else’s application.


最后,您需要确保与维护其他应用程序的团队合作,就可以进行哪些更改达成一致。管理增量更改的一种方法是让应用程序与数据库的多个版本一起工作,这样数据库就可以独立于它所依赖的应用程序进行迁移。此技术对于零停机时间发布也很有用,我们将在下一节中详细介绍。

Finally, you need to ensure that you work with the teams maintaining the other applications to agree on which changes can be made. One way to manage incremental change is to make applications work with multiple versions of your database, so that the database can be migrated independently of the applications it depends on. This technique is also useful for zero-downtime releases, which we describe in more detail in the next section.

回滚数据库和零停机发布

Rolling Back Databases and Zero-Downtime Releases

一旦您为应用程序的每个版本都设置了前滚和回滚脚本,如上一节所述,在部署时使用 DbDeploy 等应用程序将现有数据库迁移到应用程序所需的正确版本就相对容易了。您正在部署的应用程序的版本。

Once you have roll-forward and roll-back scripts for each version of your application, as described in the previous section, it is relatively easy to use an application like DbDeploy at deploy time to migrate your existing database to the correct version required by the version of the application you are deploying.

但是,有一种特殊情况:部署到生产环境。有两个常见的要求对生产部署施加了额外的限制:能够在不丢失升级后执行的事务的情况下回滚,以及根据要求严格的 SLA 保持应用程序可用的必要性,这称为热部署或零停机发布。

However, there is a special case: deployment to production. There are two common requirements which impose extra constraints on a deployment to production: the ability to roll back without losing transactions that have been performed since the upgrade, and the necessity to keep the application available according to a demanding SLA, known as hot deployment or zero-downtime releases.

回滚而不丢失数据

Rolling Back without Losing Data

在回滚的情况下,您的回滚脚本(如上一节所述)通常可以设计为保留升级发生后发生的任何事务。特别是,如果您的回滚脚本满足以下条件,应该没有问题:

In the case of a rollback, your roll-back scripts (as described in the previous section) can usually be designed to preserve any transactions that occur after the upgrade took place. In particular, there should be no problem if your roll-back scripts satisfy the following criteria:

• 它们涉及不丢失任何数据的模式更改(例如规范化或非规范化,或在表之间移动列)。在这种情况下,您只需运行回滚脚本。

• They involve schema changes that do not lose any data (such as a normalization or denormalization, or moving a column between tables, for example). In this case, you simply run the roll-back scripts.

• 他们删除了一些只有新系统才能理解的数据,但这些数据是否丢失并不重要。在这种情况下,再次简单地运行回滚脚本。

• They delete some data that only the new system understands, but it is not critical if this data is lost. In this case, again, simply run the roll-back scripts.

但是,在某些情况下,仅运行回滚脚本是不可能的。

However, there are some circumstances in which just running the roll-back scripts will not be possible.

• 回滚涉及从临时表中添加回数据。在这种情况下,自升级以来添加的新记录可能会违反完整性约束。

• Rolling back involves adding back in data from temporary tables. In this case, integrity constraints could be violated by the new records that have been added since the upgrade.

• 回滚涉及从新事务中删除系统无法接受的丢失数据。

• Rolling back involves deleting data from new transactions that it is unacceptable for the system to lose.

在这种情况下,有一些解决方案可用于回滚到应用程序的先前版本。

In this case, there are a few solutions that can be used to roll back to a previous version of the application.

一种解决方案是缓存您不想丢失的事务,并提供一种重播它们的方法。当您将数据库和应用程序升级到新版本时,请确保为进入新系统的每笔交易制作一份副本。这可以通过记录通过用户界面的事件,通过拦截在系统组件之间传递的更粗粒度的消息(如果您的应用程序使用事件驱动范例,则相对容易),或者通过实际复制每个从事务日志发生的数据库事务。一旦成功重新部署应用程序,就可以回放这些事件。当然,这种方法需要仔细的设计和测试才能起作用,但如果您确实需要确保在回滚时不会丢失数据,那么这是一个可以接受的权衡。

One solution is to cache transactions that you do not want to lose, and provide a way to replay them. When you upgrade your database and application to the new version, ensure you take a copy of each transaction that goes into the new system. This can be done by recording the events that come through the user interface, by intercepting the more coarse-grained messages that pass between the components of your system (relatively easy if your application uses an event-driven paradigm), or by actually copying each database transaction that occurs from the transaction log. These events can be played back once the application has been successfully redeployed. Of course this approach requires careful design and testing to work, but that can be an acceptable tradeoff if you really need to ensure there is no data loss in the event of a rollback.

如果您正在使用蓝绿部署(请参阅第 10 章“部署和发布应用程序”),则可以采用第二种解决方案。为了刷新您的记忆,在蓝绿部署中,应用程序的旧版本和新版本并排运行,一个在蓝色环境中,另一个在绿色环境中。“发布”简单来说就是将用户请求从旧版本切换到新版本,“回滚”就是将用户请求切换回旧版本。

A second solution can be employed if you are using blue-green deployments (see Chapter 10, “Deploying and Releasing Applications”). To refresh your memory, in blue-green deployments both the old and the new versions of your application are run side by side, one in the blue environment, the other in the green environment. “Releasing” simply means switching user requests from the old version to the new version, and “rolling back” means switching them back to the old version.

在蓝绿部署中,需要在发布时安排生产数据库(假设它是蓝色数据库)的备份。如果您的数据库不允许热备份,或者有一些其他限制阻止了热备份,您将需要将您的应用程序置于只读模式以便可以执行备份。然后将此备份还原到绿色数据库,并在其上执行迁移。然后,作为发布过程的一部分,用户将切换到绿色环境。

In blue-green deployments, a backup of the production database (let’s assume it’s the blue database) needs to be scheduled at release time. If your database doesn’t allow hot backups, or there is some other constraint that prevents this, you will need to put your application into read-only mode so the backup can be performed. This backup is then restored onto the green database, and the migration performed on it. Users are then switched to the green environment as part of the release process.

如果需要执行回滚,用户只需切换回蓝色环境即可。然后可以恢复绿色环境数据库中的新事务,在尝试另一次升级之前重新应用到蓝色数据库,或者在再次执行升级后重新应用。

If a rollback needs to be performed, users are simply switched back to the blue environment. New transactions from the green environment’s database can then be recovered, either to be reapplied to the blue database before another upgrade is attempted, or to be reapplied once the upgrade is performed again.

一些系统拥有如此多的数据,以至于在不导致不可接受的停机时间的情况下,根本不可能进行此类备份和恢复操作。在这种情况下,不能使用这种方法——而蓝绿环境仍然是可能的话,他们会在发布时切换运行的数据库,而不是拥有自己的独立数据库。

Some systems have so much data that such backup and restore operations are simply not possible without incurring unacceptable levels of downtime. In this case, this approach cannot be used—while blue-green environments are still possible, they switch which database they run against at release time, instead of having independent databases of their own.

将应用程序部署与数据库迁移解耦

Decoupling Application Deployment from Database Migration

图 12.1 将数据库迁移与应用程序部署解耦

Figure 12.1 Decoupling database migration from application deployment

图片

但是,还有第三种方法可用于管理热部署。就是将数据库迁移过程与应用部署过程解耦,独立执行,如图12.1所示。此解决方案还适用于管理协调的更改,以及第 10 章“部署和发布应用程序”中描述的蓝绿部署和金丝雀发布模式。

However, there is a third approach that can be used to manage hot deployments. It is to decouple the database migration process from the application deployment process and perform them independently, as shown in Figure 12.1. This solution is also applicable to managing orchestrated changes, as well as to the blue-green deployment and canary releasing patterns described in Chapter 10, “Deploying and Releasing Applications.”

如果您经常发布,则不需要为应用程序的每个版本迁移数据库。当您确实需要迁移您的数据库时,您必须确保它同时适用于新版本和当前版本,而不是让应用程序仅适用于新版本的数据库。在图中,应用程序的版本 241 旨在与当前部署的数据库版本(版本 14)和新版本(版本 15)一起使用。

If you are releasing frequently, you do not need to migrate your database for every release of your application. When you do need to migrate your database, instead of having the application work only with the new version of the database, you must ensure it works with both the new version and the current version. In the diagram, version 241 of the application is designed to work with both the currently deployed version of the database, version 14, and the new version, version 15.

您部署应用程序的这个过渡版本,并让它与当前版本的数据库一起工作。当您确定新版本的应用程序稳定且不需要回滚时,您可以将数据库升级到新版本(图中的版本 15)。当然,您需要在这样做之前对其进行备份。然后,当要部署的下一个版本的应用程序准备就绪时(图中的版本 248),您可以部署它而无需迁移数据库。这个版本的应用程序只需要与数据库的版本 15 一起工作。

You deploy this transitional version of your application and have it work against the current version of the database. When you’re sure the new version of the application is stable and doesn’t need to be rolled back, you can upgrade the database to the new version (version 15 in the diagram). Of course, you need to back it up before you do so. Then, when the next version of the application to be deployed is ready (version 248 in the diagram), you can deploy it without having to migrate the database. This version of the application just needs to work with version 15 of the database.

在难以将数据库恢复到早期版本的情况下,此方法也很有用。我们在新版本的数据库进行了一些重大更改的情况下使用了它,包括对丢失信息的数据库模式的更改。因此,升级会损害我们在出现问题时恢复到软件早期版本的能力。我们部署了我们应用程序的新版本,它具有向后兼容性,可以在不部署新数据库更改的情况下针对旧版本的数据库模式运行。然后我们可以观察新版本的行为,确认它没有引入任何需要恢复到前一个版本的问题。最后,一旦我们有信心,我们也部署了数据库更改。

This approach can also be useful in circumstances where reverting your database to an earlier version is difficult. We have used it in a situation where the new version of the database made some significant changes, including changes to the database schema that lost information. As a result, the upgrade would compromise our ability to revert to an earlier version of the software if a problem occurred. We deployed the new version of our application which, being backwardcompatible, could run against the database schema of the old version without deploying the new database changes. We could then observe the behavior of the new version, confirming that it didn’t introduce any problems that warranted reversion to the previous one. Finally, once we were confident, we deployed the database changes too.

向前兼容性也不是一个通用的解决方案,尽管对于普通的、正常的更改来说,它是一种有用的策略。在此上下文中,前向兼容性是应用程序的较早版本针对较新版本的数据库模式工作的能力。自然地,如果新模式中有额外的字段或表,这些将被未设计为与它们一起使用的应用程序版本忽略。尽管如此,两个版本共有的数据库模式部分保持不变。

Forward compatibility is also not a generic solution, though for the run-ofthe-mill, normal changes it is a useful strategy to adopt. Forward compatibility, in this context, is the ability of an earlier version of an application to work against the database schema of a later version. Naturally, if there are additional fields or tables in the new schema, these will be ignored by the application versions that aren’t designed to work with them. Nevertheless, those parts of the database schema that are common to the two versions remain the same.

最好将此作为大多数更改的默认方法。也就是说,大多数更改应该是附加的,向我们的数据库添加新表或列,但尽可能不更改现有结构。

It is best to adopt this as the default approach for most changes. That is, most changes should be additive, adding new tables or columns to our database, but not changing existing structures, where possible.


图片

另一种管理数据库更改和重构的方法是使用存储过程和视图形式的抽象层 [cVVuV0]。如果应用程序通过这样的抽象层访问数据库,则可以对底层数据库对象进行更改,同时保持视图和存储过程向应用程序呈现的界面不变。这是第 349 页的“抽象分支”部分中描述的“抽象分支”示例

Another approach to managing database changes and refactorings is to use an abstraction layer, in the form of stored procedures and views [cVVuV0]. If the application accesses the database through such an abstraction layer, it is possible to make changes to the underlying database objects while keeping the interface presented to the application by the views and stored procedures constant. This is an example of “branch by abstraction,” described in the “Branch by Abstraction” section on page 349.


管理测试数据

Managing Test Data

测试数据对于所有测试都很重要,无论是手动测试还是自动测试。哪些数据可以让我们模拟与系统的常见交互?哪些数据代表边缘情况,可以证明我们的应用程序适用于异常输入?哪些数据会迫使应用程序进入错误状态,以便我们可以评估其在这些情况下的响应?这些问题与我们测试系统的每个级别都相关,但是对于依赖于我们的测试数据在某处数据库中的测试提出了一组特定的问题。

Test data is important for all tests, whether manual or automated. What data will allow us to simulate common interactions with the system? What data represents edge cases that will prove that our application works for unusual inputs? What data will force the application into error conditions so that we can evaluate its response under those circumstances? These questions are relevant at every level at which we test our system, but pose a particular set of problems for tests that rely on our test data being in a database somewhere.

我们将在本节中强调两个问题。首先是测试性能。我们希望确保我们的测试尽可能快地运行。在单元测试的情况下,这意味着要么根本不针对数据库运行,要么针对内存数据库运行。对于其他类型的测试,这意味着管理测试仔细数据,除了少数有限的情况外,当然不使用生产数据库的转储。

There are two concerns that we will highlight in this section. First is test performance. We want to make sure our tests run as fast as possible. In the case of unit tests, that means either not running against a database at all, or running against an in-memory database. For other types of tests, it means managing test data carefully, and certainly not using a dump of the production database except in a few limited cases.

第二个问题是测试隔离。理想的测试在定义明确的环境中运行,其输入受到控制,因此我们可以轻松评估其输出。另一方面,数据库是一种持久的信息存储,允许更改在测试调用之间持续存在——除非您明确地采取措施来阻止它。这会使起始条件不明确,尤其是当您无法直接控制测试的执行顺序时(通常是这种情况)。

A second issue is test isolation. An ideal test runs in a well-defined environment whose inputs are controlled so that we can easily evaluate its outputs. A database, on the other hand, is a durable store of information that allows changes to persist between test invocations—unless you explicitly do something to prevent it. This can make the starting conditions unclear, particularly when you may have no direct control over the execution order of your tests, which is usually the case.

为单元测试伪造数据库

Faking the Database for Unit Tests

单元测试不要针对真实数据库运行,这一点很重要。通常单元测试会注入测试替身来代替与数据库对话的服务。但是,如果这不可能(例如,如果您想测试这些服务),您可以应用另外两种策略。

It is important that unit tests do not run against a real database. Usually unit tests will inject test doubles in place of services that talk to databases. However, if this is not possible (for example, if you want to test these services), there are two other strategies that you can apply.

图 12.2 抽象数据库访问

Figure 12.2 Abstracting database access

图片

一种是用测试替身替换您的数据库访问代码。将访问数据库的代码封装在您的应用程序中是一种很好的做法。实现此目标的常用模式是存储库模式 [blIgdc]。在这种模式中,您在数据访问代码之上创建了一个抽象层,它将您的应用程序与正在使用的数据库分离(这实际上是第 13 章“管理组件和依赖项”中描述的抽象模式分支的应用程序)。完成后,您可以将数据访问代码换成测试替身。这种方法如图 12.2所示。

One is to replace your database access code with a test double. It is good practice to encapsulate code that accesses the database within your application. A commonly used pattern to achieve this objective is the repository pattern [blIgdc]. In this pattern, you create an abstraction layer above your data access code which decouples your application from the database being used (this is actually an application of the branch by abstraction pattern described in Chapter 13, “Managing Components and Dependencies”). Once this is done, you can swap out your data access code for a test double. This approach is shown in Figure 12.2.

正如我们所描述的那样,该策略不仅提供了一种支持测试的机制,而且还鼓励将重点放在系统的业务行为上,使其与数据存储需求分开。它还倾向于确保将所有数据访问代码保存在一起,从而使维护代码库更加容易。这种好处的组合通常超过维护一个单独的层以提供相关抽象的相对较小的成本。

This strategy not only provides a mechanism to support testing, as we have described, but also encourages a focus on the business behavior of the system separately from its data storage needs. It also tends to ensure that all data access code is kept together, thus making maintaining the codebase easier. This combination of benefits usually outweighs the relatively small cost of maintaining a separate layer to provide the relevant abstraction.

如果不使用这种方法,仍然可以伪造数据库。有几个开源项目提供内存中关系数据库(看看 H2、SqlLite 或 JavaDB)。通过使应用程序与之交互的数据库实例可配置,您可以组织单元测试以针对内存数据库运行。然后可以针对更常见的基于磁盘的数据库运行验收测试。同样,这种方法有一些次要的好处:它鼓励以稍微解耦的方式编写代码,至少在某种程度上它可以针对两个不同的数据库实现工作。这反过来又确保了未来的更改——更新版本,甚至可能是不同的 RDBMS 供应商——将更容易完成。

Where this approach is not used, it is still possible to fake the database. There are several open source projects that provide an in-memory relational database (take a look at H2, SqlLite, or JavaDB). By making the database instance that the application interacts with configurable, you can organize your unit tests to run against the in-memory database. Then the acceptance tests can run against the more usual disk-based database. Again, this approach has some subsidiary benefits: It encourages code to be written in a slightly more decoupled way, at least to the degree that it will work against two different database implementations. This, in turn, ensures that future changes—to a newer version, or even perhaps to a different RDBMS vendor—will be easier to accomplish.

管理测试和数据之间的耦合

Managing the Coupling between Tests and Data

当涉及到测试数据时,测试套件中的每个单独测试都有一些它可以依赖的状态是很重要的。在编写验收标准的“给定、何时、然后”格式中,测试开始时的初始状态是“何时”。只有当起始状态已知时,您才能将其与测试完成后的状态进行比较,从而验证被测行为。

When it comes to test data, it is important that each individual test in a test suite has some state on which it can depend. In the “given, when, then” format for writing acceptance criteria, the initial state when the test starts is the “when.” Only when the starting state is known can you compare it against the state after the test has finished, and thus verify the behavior under test.

这对于单个测试来说很简单,但需要一些思考才能实现测试套件,特别是对于依赖数据库的测试。

This is simple for a single test, but requires some thought to achieve for suites of tests, particularly for tests that rely upon a database.

大体上,可以通过三种方法来管理测试状态。

Broadly, there are three approaches to managing state for tests.

测试隔离:组织测试,使每个测试的数据仅对该测试可见。

Test isolation: Organize tests so that each test’s data is only visible to that test.

适应性测试:每个测试都旨在评估其数据环境并调整其行为以适应它所看到的数据。

Adaptive tests: Each test is designed to evaluate its data environment and adapt its behavior to suit the data it sees.

测试顺序:测试被设计为以已知顺序运行,每个测试的输入取决于其前身的输出。

Test sequencing: Tests are designed to run in a known sequence, each depending, for inputs, on the outputs of its predecessors.

一般来说,我们强烈推荐这些方法中的第一种。将测试彼此隔离使它们更加灵活,而且重要的是,能够并行运行以优化测试套件性能。

In general, we strongly recommend the first of these approaches. Isolating tests from one another makes them more flexible as well as, importantly, capable of being run in parallel to optimize test suite performance.

其他两种方法都是可能的,但根据我们的经验,不能很好地扩展。随着测试套件变得越来越大,它包含的交互也越来越复杂,这两种策略往往会导致很难检测和纠正的故障。测试之间的交互变得越来越模糊,维护一套工作测试的成本开始增加。

Both of the other approaches are possible but, in our experience, don’t scale up well. As the suite of tests becomes larger and the interactions it embodies more complex, both of these strategies tend to result in failures that are very hard to detect and correct. Interactions between tests become increasingly obscure, and the cost of maintaining a working suite of tests begins to grow.

测试隔离

Test Isolation

测试隔离是一种确保每个单独的测试都是原子的策略。也就是说,它不应依赖于其他测试的结果来确定其状态,并且其他测试不应以任何方式影响其成功或失败。这种隔离级别对于提交测试来说相对简单,即使是那些测试数据库中数据持久性的测试也是如此。

Test isolation is a strategy for ensuring that each individual test is atomic. That is, it should not depend on the outcome of other tests to establish its state, and other tests should not affect its success or failure in any way. This level of isolation is relatively simple to achieve for commit tests, even those that test the persistence of data in a database.

最简单的方法是确保在测试结束时,您始终将数据库中的数据返回到测试运行之前的状态。您可以手动执行此操作,但最简单的方法是依赖大多数 RDBMS 系统的事务性质。

The simplest approach is to ensure that, at the conclusion of the test, you always return the data in the database to the state it was in before the test was run. You can do this manually, but the simplest approach is to rely upon the transactional nature of most RDBMS systems.

对于与数据库相关的测试,我们在测试开始时创建一个事务,在该事务中执行我们需要的所有操作和与数据库的交互,并在测试结束时(无论是否通过),我们回滚事务。这使用数据库系统的事务隔离属性来确保没有其他测试或数据库用户会看到测试所做的更改。

For database-related tests, we create a transaction at the beginning of the test, perform all of the operations and interactions with the database that we require within that transaction, and at the conclusion of the test (whether it passed or not), we roll back the transaction. This uses the transaction isolation properties of the database system to ensure that no other tests or users of the database will see the changes that the test makes.

测试隔离的第二种方法是对数据执行某种功能分区。这是提交和验收测试的有效策略。对于需要修改系统状态作为结果的测试,让您在测试中创建的主要实体遵循一些特定于测试的命名约定,以便每个测试只会查找和查看专门为其创建的数据. 我们在第 204 页的“验收测试中的状态”部分更详细地描述了这种方法

A second approach to test isolation is to perform some kind of functional partitioning of the data. This is an effective strategy for both commit and acceptance tests. For tests that need to modify the state of the system as an outcome, make the principal entities that you create in your tests follow some test-specific naming convention, so that each test will only look for and see data that was created specifically for it. We describe this approach in more detail in the “State in Acceptance Tests” section on page 204.

通过对数据进行分区来找到合适的测试隔离级别的难易程度在很大程度上取决于问题域。如果您的域合适,这是保持测试彼此独立的一种优秀而简单的策略。

How easy it is to find a suitable level of test isolation through partitioning the data depends a lot on the problem domain. If you domain is suitable, this is an excellent and simple strategy for keeping tests independent of one another.

安装和拆卸

Setup and Tear Down

无论选择何种策略,在运行测试之前为测试建立一个已知良好的起始位置,并在其结束时重新建立,对于避免交叉测试依赖性至关重要。

Whatever strategy is chosen, the establishment of a known-good starting position for the test before it is run, and its reestablishment at its conclusion, is vital to avoid cross-test dependencies.

对于隔离良好的测试,通常需要一个设置阶段来用相关的测试数据填充数据库。这可能涉及创建一个将在测试结束时回滚的新事务,或者简单地写入一些测试特定信息的记录。

For well-isolated tests, a setup stage is usually needed to populate the database with relevant test data. This may involve creating a new transaction that will be rolled back at the conclusion of the test, or simply writing a few records of test-specific information.

自适应测试将评估数据环境,以便在启动时建立已知的起始位置。

Adaptive tests will be evaluating the data environment in order to establish the known starting position at startup.

相干测试场景

Coherent Test Scenarios

人们常常想创建一个连贯的“故事”,测试将随之而来。这种方法的目的是创建的数据是连贯的,因此设置和最小化测试用例的拆除。这应该意味着每个测试本身更简单一些,因为它不再负责管理自己的测试数据。这也意味着整个测试套件将运行得更快,因为它不会花费大量时间来创建和销毁测试数据。

There is often a temptation to create a coherent “story” that tests will follow. The intent of this approach is that the data created is coherent, so setting up and tearing down of test cases is minimized. This should mean that each test is, in itself, a little simpler, since it is no longer responsible for managing its own test data. This also means that the test suite as a whole will run faster because it doesn’t spend a lot of time creating and destroying test data.

有时这种方法很诱人,但在我们看来,这是一种应该抵制的诱惑。这种策略的问题在于,在争取一个连贯的故事时,我们将测试紧密地结合在一起。这种紧密耦合有几个重要的缺点。随着测试套件规模的增长,测试变得更加难以设计。当一个测试失败时,它会对依赖其输出的后续测试产生级联效应,使它们也失败。业务场景或技术实现的变化可能导致测试套件的痛苦返工。

There are times when this approach is tempting, but in our view, it is a temptation that should be resisted. The problem with this strategy is that in striving for a coherent story we tightly couple tests together. There are several important drawbacks to this tight coupling. Tests become more difficult to design as the size of the test suite grows. When one test fails, it can have a cascade effect on subsequent tests that depend on its outputs, making them fail too. Changes in the business scenario, or the technical implementation, can lead to painful reworking of the test suite.

但更根本的是,这种顺序视图并不能真正代表测试的真实情况。在大多数情况下,即使应用程序包含一个清晰的步骤序列,在每个步骤中我们都想探索成功发生了什么、失败发生了什么、边界条件发生了什么等等。我们应该在非常相似的启动条件下运行一系列不同的测试。一旦我们开始支持这一观点,我们就必须建立和重建测试数据环境,因此我们又回到了创建自适应测试或相互隔离测试的领域。

More fundamentally though, this sequential ordered view doesn’t really represent the reality of testing. In most cases, even where there is a clear sequence of steps that the application embodies, at each step we want to explore what happens for success, what happens for failures, what happens for boundary conditions, and so on. There is a range of different tests that we should be running with very similar startup conditions. Once we move to support this view, we will necessarily have to establish and reestablish the test data environment, so we are back in the realm of either creating adaptive tests or isolating tests from one another.

数据管理和部署管道

Data Management and the Deployment Pipeline

创建和管理用于自动化测试的数据可能是一项巨大的开销。让我们退后一步。我们测试的重点是什么?

Creating and managing data to use with automated tests can be a significant overhead. Let us take a step back for a moment. What is the focus of our testing?

我们测试我们的应用程序以断言它具有我们想要的各种行为特征。我们运行单元测试来保护我们自己免受无意中做出破坏我们应用程序的更改的影响。我们运行验收测试来断言应用程序为用户提供了预期的价值。我们执行容量测试以断言应用程序满足我们的容量要求。也许我们运行一套集成测试来确认我们的应用程序与其依赖的服务正确通信。

We test our application to assert that it possesses a variety of behavioral characteristics that we desire. We run unit tests to protect ourselves from the effects of inadvertently making a change that breaks our application. We run acceptance tests to assert that the application delivers the expected value to users. We perform capacity testing to assert that the application meets our capacity requirements. Perhaps we run a suite of integration tests to confirm that our application communicates correctly with services it depends on.

部署管道中每个测试阶段所需的测试数据是什么,我们应该如何管理它?

What is the test data that we need for each of these testing stages in the deployment pipeline, and how should we manage it?

提交阶段测试中的数据

Data in Commit Stage Tests

提交测试是部署管道中的第一个阶段。提交测试快速运行的过程至关重要。提交阶段是开发人员在继续之前等待通过的阶段。添加到这个阶段的每 30 秒都是昂贵的。

Commit testing is the first stage in the deployment pipeline. It is vital to the process that commit tests run quickly. The commit stage is the point at which developers are sitting waiting for a pass before moving on. Every 30 seconds added to this stage are costly.

除了提交阶段测试的直接性能之外,提交测试是防止对系统进行无意更改的主要防御措施。这些测试与实现的细节联系得越多,它们在扮演那个角色。问题在于,当您需要重构系统某些方面的实现时,您希望测试能保护您。如果测试与实现的细节联系得太紧密,你会发现对实现做一个小的改变会导致围绕它的测试发生更大的变化。不是保护系统的行为,从而促进必要的改变,而是与实施细节过于紧密耦合的测试将抑制改变。如果您被迫对测试进行重大更改以实现相对较小的实现更改,则测试无法有效地执行其可执行行为规范的角色。

In addition to the outright performance of commit stage testing, commit tests are the primary defense against inadvertent changes to the system. The more these tests are tied to the specifics of the implementation, the worse they are at performing that role. The problem is that when you need to refactor the implementation of some aspect of your system, you want the test to protect you. If the tests are too tightly linked to the specifics of the implementation, you will find that making a small change in implementation results in a bigger change in the tests that surround it. Instead of defending the behavior of the system, and so facilitating necessary change, tests that are too tightly coupled to the specifics of the implementation will inhibit change. If you are forced to make significant changes to tests for relatively small changes in implementation, the tests are not performing effectively their role of executable specifications of behavior.

这在有关数据和数据库的章节中听起来可能有些抽象,但测试中的紧耦合通常是过于复杂的测试数据的结果。

This may sound somewhat abstract in a chapter on data and databases, but tight coupling in tests is often the result of overelaborate test data.

这是持续集成过程提供一些看似无关的积极行为的关键点之一。好的提交测试避免了复杂的数据设置。如果您发现自己正在努力为特定测试建立数据,这肯定表明您的设计需要更好地分解。您需要将设计拆分为更多组件并独立测试每个组件,使用测试替身模拟依赖关系,如第 180 页“使用测试替身”部分所述。

This is one of those key points where the process of continuous integration delivers some seemingly unrelated positive behaviors. Good commit tests avoid elaborate data setup. If you find yourself working hard to establish the data for a particular test, it is a sure indicator that your design needs to be better decomposed. You need to split the design into more components and test each independently, using test doubles to simulate dependencies, as described in the “Using Test Doubles” section on page 180.

最有效的测试并不是真正的数据驱动;他们使用最少的测试数据来断言被测单元表现出预期的行为。那些确实需要更复杂的数据来演示所需行为的测试应该仔细创建它,并尽可能重用测试助手或固定装置来创建它,以便系统支持的数据结构设计的变化并不代表对系统可测试性的灾难性打击。

The most effective tests are not really data-driven; they use the minimum of test data to assert that the unit under test exhibits the expected behavior. Those tests that do need more sophisticated data in place to demonstrate desired behavior should create it carefully and, as far as possible, reuse the test helpers or fixtures to create it, so that changes in the design of the data structures that the system supports do not represent a catastrophic blow to the system’s testability.

在我们的项目中,我们经常会隔离创建此类常用数据结构的测试实例的代码,并在许多不同的测试用例之间共享它们。我们可能有一个CustomerHelperCustomerFixture类,它们将简化为我们的测试创建Customer对象,因此它们的创建方式与每个Customer的标准默认值集合一致。然后每个测试都可以定制数据以满足其需求,但它从已知的、一致的状态开始。

In our projects, we will often isolate the code creating test instances of such commonly used data structures and share them between many different test cases. We may have a CustomerHelper or CustomerFixture class that will simplify the creation of Customer objects for our tests, so they are created in a consistent manner with a collection of standard default values for each Customer. Each test can then tailor the data to meet its needs, but it starts from a known, consistent state.

从根本上说,我们的目标是将每个测试的特定数据最小化到直接影响测试试图建立的行为的数据。这应该是您编写的每个测试的目标。

Fundamentally, our objective is to minimize the data specific to each test to that which directly impacts the behavior the test is attempting to establish. This should be a goal for every test that you write.

验收测试中的数据

Data in Acceptance Tests

与提交测试不同,验收测试是系统测试。这意味着他们的测试数据必然更复杂,如果你想避免测试变得笨拙,就需要更仔细地管理。同样,目标是尽可能减少我们的测试对大型复杂数据结构的依赖。这种方法与提交阶段测试基本相同:我们的目标是在创建测试用例时实现重用,并最大限度地减少每个测试对测试数据。我们应该创建足够的数据来测试系统的预期行为。

Acceptance tests, unlike commit tests, are system tests. This means that their test data is necessarily more complex and needs to be managed more carefully if you want to avoid the tests becoming unwieldy. Again, the goal is to minimize the dependence of our tests on large complex data structures as far as possible. This approach is fundamentally the same as for commit stage tests: We aim to achieve reuse in the creation of our test cases and to minimize each test’s dependence on test data. We should be creating just enough data to test the expected behavior of the system.

在考虑如何为验收测试设置应用程序状态时,区分三种数据会很有帮助。

When considering how to set up the state of the application for an acceptance test, it is helpful to distinguish between three kinds of data.

1.特定于测试的数据:这是驱动被测行为的数据。它代表被测案例的细节。

1. Test-specific data: This is the data that drives the behavior under test. It represents the specifics of the case under test.

2.测试参考数据:通常有第二类数据与测试相关,但实际上与被测行为关系不大。它需要在那里,但它是配角的一部分,而不是主要角色。

2. Test reference data: There is often a second class of data that is relevant for a test but actually has little bearing upon the behavior under test. It needs to be there, but it is part of the supporting cast, not the main player.

3.应用程序参考数据:通常,有些数据与被测行为无关,但需要存在这些数据才能启动应用程序。

3. Application reference data: Often, there is data that is irrelevant to the behavior under test, but that needs to be there to allow the application to start up.

特定于测试的数据应该是唯一的,并使用测试隔离策略来确保测试在不受其他测试的副作用影响的定义明确的环境中开始。

Test-specific data should be unique and use test isolation strategies to ensure that the test starts in a well-defined environment that is unaffected by the side effects of other tests.

可以通过使用预填充的种子数据来管理测试参考数据,这些数据在各种测试中重复使用,以建立测试运行的一般环境,但不受测试操作的影响。

Test reference data can be managed by using prepopulated seed data that is reused in a variety of tests to establish the general environment in which the tests run, but which remains unaffected by the operation of the tests.

应用程序参考数据可以是任何值,甚至可以是空值,前提是所选择的值继续对测试结果没有影响。

Application reference data can be any value at all, even null values, provided the values chosen continue to have no effect on the test outcome.

应用程序参考数据和(如果适用)测试参考数据(应用程序启动所需的任何数据)都可以以数据库转储的形式保存。当然,您必须对这些进行版本控制,并确保将它们作为应用程序设置的一部分进行迁移。这是测试自动数据库迁移策略的有用方法。

Application reference data and, if applicable, test reference data—whatever is needed for your application to start up—can be kept in the form of database dumps. Of course you will have to version these and ensure they are migrated as part of the application setup. This is a useful way to test your automated database migration strategy.

这种分类并不严谨。通常,在特定测试的上下文中,数据类别之间的界限可能会有些模糊。然而,我们发现它是一个有用的工具,可以帮助我们专注于我们需要主动管理的数据,以确保我们的测试是可靠的,而不是只需要存在的数据。

This categorization is not rigorous. Often, the boundaries between classes of data may be somewhat blurred in the context of a specific test. However, we have found it a useful tool to help us focus on the data that we need to actively manage to ensure that our test is reliable, as opposed to the data that simply needs to be there.

从根本上说,让测试过于依赖代表整个应用程序的数据“宇宙”是错误的。能够以某种程度的隔离来考虑每个测试是很重要的,否则整个测试套件会变得太脆弱并且会随着数据的每个小变化而不断失败。

Fundamentally, it is a mistake to make tests too dependent on the “universe” of data that represents the entire application. It is important to be able to consider each test with some degree of isolation, or the entire test suite becomes too brittle and will fail constantly with every small change in data.

但是,与提交测试不同,我们不建议使用应用程序代码或数据库转储将应用程序置于测试的正确初始状态。相反,为了与测试的系统级性质保持一致,我们建议使用应用程序的 API 将其置于正确的状态。

However, unlike commit tests, we do not recommend using application code or database dumps to put the application into the correct initial state for the test. Instead, in keeping with the system-level nature of the tests, we recommend using the application’s API to put it into the correct state.

这有几个优点:

This has several advantages:

• 使用应用程序代码或绕过应用程序业务逻辑的任何其他机制,会使系统处于不一致状态。使用应用程序的 API 可确保应用程序在验收测试期间永远不会处于不一致的状态。

• Using the application code, or any other mechanism that bypasses the application’s business logic, can put the system into an inconsistent state. Using the application’s API ensures that the application is never in an inconsistent state during acceptance tests.

• 数据库或应用程序本身的重构不会对验收测试产生影响,因为根据定义,重构不会改变应用程序公共API 的行为。这将使您的验收测试明显不那么脆弱。

• Refactorings of the database or the application itself will have no effect on the acceptance tests since, by definition, refactorings do not alter the behavior of the application’s public API. This will make your acceptance tests significantly less brittle.

• 您的验收测试也将作为应用程序API 的测试。

• Your acceptance tests will also serve as tests of your application’s API.

容量测试数据

Data in Capacity Tests

容量测试提出了大多数应用程序所需数据的规模问题。这个问题表现在两个方面:为测试提供足够数量的输入数据的能力,以及提供合适的参考数据以同时支持许多被测案例的能力。

Capacity tests present a problem of scale in the data required by most applications. This problem exhibits itself in two areas: the ability to deliver a sufficient volume of input data for the test and the provision of suitable reference data to support many cases under test simultaneously.

正如第 9 章“测试非功能性需求”中所述,我们将容量测试视为重新运行验收测试的主要练习,但同时针对许多情况。如果您的应用程序支持下订单的概念,我们预计在容量测试时会同时下很多订单。

As described in Chapter 9, “Testing Nonfunctional Requirements,” we see capacity testing as primarily an exercise in rerunning acceptance tests, but for many cases at the same time. If your application supports the concept of placing an order, we would expect to be placing many orders simultaneously when we are capacity-testing.

我们的首选是使用交互模板等机制自动生成这些大量数据,包括输入和引用,在第 241 页的“使用记录的交互模板”部分中有更详细的描述。

Our preference is to automate the generation of these large volumes of data, both input and reference, using mechanisms like interaction templates, described in more detail in the “Using Recorded Interaction Templates” section on page 241.

实际上,这种方法允许我们放大我们创建和管理的数据以支持我们的验收测试。这种数据重用策略是我们倾向于尽可能广泛应用的策略,我们的基本原理是我们编码为验收测试套件一部分的交互,以及与这些交互相关的数据,主要是行为的可执行规范系统的。如果我们的验收测试在这个角色中有效,它们就会捕获我们的应用程序支持的重要交互。如果他们不对系统的重要行为进行编码,那么我们将要作为容量测试的一部分进行测量的系统行为就会出现问题。

This approach, in effect, allows us to amplify the data that we create and manage to support our acceptance tests. This strategy of data reuse is one that we tend to apply as widely as we can, our rationale being that the interactions that we encode as part of our acceptance test suite, and the data associated with those interactions, are primarily executable specifications of the behavior of the system. If our acceptance tests are effective in this role, they capture the important interactions that our application supports. Something is wrong if they don’t encode the important behaviors of the system that we will want to measure as part of our capacity test.

此外,如果我们有适当的机制和流程来保持这些测试随着应用程序的不断发展而运行,那么当涉及到容量测试时,为什么要放弃所有这些并重新开始,或者实际上当涉及到任何其他验收后测试阶段?

Further, if we have mechanisms and processes in place to keep these tests running in line with the application as it evolves over time, why dump all of that and start again when it comes to capacity testing, or indeed when it comes to any other postacceptance test stage?

那么,我们的策略是依赖我们的验收测试作为与我们感兴趣的系统交互的记录,然后使用该记录作为后续测试阶段的起点。

Our strategy, then, is to rely on our acceptance tests as a record of the interactions with our system that are of interest and then use that record as a starting point for subsequent test stages.

对于容量测试,我们使用工具获取与选定验收测试相关的数据并将其扩展到许多不同的“案例”,以便我们可以根据该测试应用与系统的许多交互。

For capacity testing, we use tools that will take the data associated with a selected acceptance test and scale it up to many different “cases” so that we can apply many interactions with the system based on that one test.

这种测试数据生成方法使我们能够将我们的能力测试数据管理工作集中在数据的核心上,这些数据对于每个单独的交互来说都是独一无二的。

This approach to test data generation allows us to concentrate our capacity test data management efforts on the core of the data that is, of necessity, unique to each individual interaction.

其他测试阶段的数据

Data in Other Test Stages

至少在设计理念层面,如果不是特定的技术方法,我们将相同的方法应用于所有验收后的自动化测试阶段。我们的目标是重用作为我们的自动化验收测试的“行为规范”,作为任何重点不是纯功能的测试的起点。

At least at the level of design philosophy, if not specific technical approach, we apply the same approach to all postacceptance automated test stages. Our aim is to reuse the “specifications of behavior” that are our automated acceptance tests as the starting point for any testing whose focus is other than purely functional.


图片

在创建 Web 应用程序时,我们使用我们的验收测试套件来导出我们的容量测试和兼容性测试。对于兼容性测试,我们针对所有流行的 Web 浏览器重新运行整个验收测试套件。这不是一个详尽的测试——它没有告诉我们任何关于可用性的信息——但如果我们在某些浏览器中进行了完全破坏用户界面的更改,它确实会向我们发出警报。由于我们重用了我们的部署机制和我们的验收测试套件,并且我们使用虚拟机来托管测试,我们执行兼容性测试的能力几乎是免费的——除了一些 CPU 时间和磁盘空间来运行测试的成本。

When creating web applications, we use our acceptance test suite to derive not only our capacity tests, but also our compatibility tests. For compatibility testing, we rerun our entire acceptance test suite against all of the popular web browsers. This is not an exhaustive test—it tells us nothing about usability—but it does give us an alarm if we have made a change that breaks the user interface altogether in some browser. Since we reuse both our deployment mechanisms and our acceptance test suite, and we use virtual machines to host the tests, our ability to perform compatibility testing comes virtually for free—except for the cost of some CPU time and disk space to run the tests.


对于手动测试阶段,例如探索性测试或用户验收测试环境,有几种测试数据的方法。一种是在一组最小的测试和应用程序参考数据中运行,以使应用程序能够在空的初始状态下启动。然后,测试人员可以对用户最初开始使用该应用程序时出现的场景进行试验。另一种方法是加载更大的数据集,以便测试人员可以执行假设应用程序已使用一段时间的场景。拥有用于集成测试的大型数据集也很有用。

For manual testing stages, such as exploratory testing or user acceptance testing environments, there are a couple of approaches to test data. One is to run in a minimal set of test and application reference data to enable the application to start up in an empty initial state. Testers can then experiment with scenarios that occur when users initially start working with the application. Another approach is to load a much larger set of data so that testers can perform scenarios that assume the application has been in use for some time. It’s also useful to have a large dataset for doing integration testing.

虽然可以为这些场景转储生产数据库,但在大多数情况下我们不建议这样做。这主要是因为数据集太大,太笨重了。迁移生产数据集有时可能需要数小时。然而,在某些情况下,使用生产转储进行测试很重要——例如,在测试生产数据库的迁移时,或者确定需要在什么时间点归档生产数据,以免过度降低应用程序的速度。

While it’s possible to take a dump of the production database for these scenarios, we do not recommend this in most cases. This is mainly because the dataset is so large as to be too unwieldy. Migrating a production dataset can sometimes take hours. Nevertheless, there are cases where it’s important to test with a dump of production—for example, when testing the migration of the production database, or determining at what point production data needs to be archived so it does not unduly slow down the application.

相反,我们建议创建自定义数据集以用于手动测试,该数据集可以基于生产数据的子集,也可以基于在运行一组自动验收或容量测试后获取的数据库转储。您甚至可以自定义容量测试框架,以生成一个数据库,该数据库代表一组用户持续使用后应用程序的真实状态。然后可以存储此数据集并作为部署到手动测试环境的一部分重复使用。当然,它需要作为此部署过程的一部分进行迁移。有时,测试人员保留多个数据库转储,用作各种测试的起点。

Instead, we recommend creating a customized dataset to use for manual testing, based either on a subset of the production data, or on a dump of the database taken after a set of automated acceptance or capacity tests have been run. You can even customize your capacity testing framework to produce a database that represents a realistic state of the application after continued use by a set of users. This dataset can then be stored and reused as part of the deployment to manual testing environments. Of course it will need to be migrated as part of this deployment process. Sometimes testers keep several database dumps around to use as starting points for various kinds of tests.

这些数据集,包括启动应用程序所需的最小数据集,也应由开发人员在其环境中使用。开发人员决不应在其环境中使用生产数据集。

These datasets, including the minimal dataset required to start the application, should also be used by developers in their environments. On no account should developers use production datasets in their environments.

概括

Summary

由于其生命周期,数据管理提出了一系列不同于我们在部署管道上下文中讨论的问题。但是,管理数据管理的基本原则是相同的。关键是要确保有一个完全自动化的过程来创建和迁移数据库。此过程用作部署过程的一部分,确保其可重复且可靠。无论是将应用程序部署到具有最小数据集的开发或验收测试环境,还是将生产数据集作为部署的一部分迁移到生产,都应该使用相同的过程。

Due to its lifecycle, the management of data presents a collection of problems different from those we have discussed in the context of the deployment pipeline. However, the fundamental principles that govern data management are the same. The key is to ensure that there is a fully automated process for creating and migrating databases. This process is used as part of the deployment process, ensuring it is repeatable and reliable. The same process should be used whether deploying the application to a development or acceptance testing environment with a minimal dataset, or whether migrating the production dataset as part of a deployment to production.

即使使用自动数据库迁移过程,仔细管理用于测试目的的数据仍然很重要。虽然生产数据库的转储可能是一个诱人的起点,但它通常太大而无用。相反,让您的测试创建它们需要的状态,并确保它们以这样一种方式执行此操作,即您的每个测试都独立于其他测试。即使是手动测试,在极少数情况下,生产数据库的转储是最佳起点。测试人员应该根据自己的目的创建和管理较小的数据集。

Even with an automated database migration process, it is still important to manage data used for testing purposes carefully. While a dump of the production database can be a tempting starting point, it is usually too large to be useful. Instead, have your tests create the state they need, and ensure they do this in such a way that each of your tests is independent of the others. Even for manual testing, there are few circumstances in which a dump of the production database is the best starting point. Testers should create and manage smaller datasets for their own purposes.

以下是本章中一些更重要的原则和实践:

Here are some of the more important principles and practices from this chapter:

• 对数据库进行版本控制并使用 DbDeploy 等工具自动管理迁移。

• Version your database and use a tool like DbDeploy to manage migrations automatically.

• 努力保持与架构更改的向前和向后兼容性,以便您可以将数据部署和迁移问题与应用程序部署问题分开。

• Strive to retain both forward and backward compatibility with schema changes so that you can separate data deployment and migration issues from application deployment issues.

• 确保测试创建它们依赖的数据作为设置过程的一部分,并且数据被分区以确保它不会影响可能同时运行的其他测试。

• Make sure tests create the data they rely on as part of the setup process, and that data is partitioned to ensure it does not affect other tests that might be running at the same time.

• 仅针对启动应用程序所需的数据以及一些非常通用的参考数据保留测试之间的设置共享。

• Reserve the sharing of setup between tests only for data required to have the application start, and perhaps some very general reference data.

• 尽可能使用应用程序的公共API 为测试设置正确的状态。

• Try to use the application’s public API to set up the correct state for tests wherever possible.

• 在大多数情况下,不要将生产数据集的转储用于测试目的。通过仔细选择较小的生产数据子集,或者从验收或容量测试运行中创建自定义数据集。

• In most cases, don’t use dumps of the production dataset for testing purposes. Create custom datasets by carefully selecting a smaller subset of production data, or from acceptance or capacity test runs.

当然,这些原则需要根据您的情况进行调整。但是,如果将它们用作默认方法,它们将帮助任何软件项目最大限度地减少最常见问题以及与自动化测试和生产环境中的数据管理相关的问题的影响。

Of course, these principles will need to be adapted to your situation. However, if they are used as the default approach, they will help any software project to minimize the effects of the most common problems and issues associated with data management in automated testing and production environments.

第 13 章管理组件和依赖关系

Chapter 13. Managing Components and Dependencies

介绍

Introduction

持续交付提供了每天多次发布新的、可用的软件版本的能力。这意味着您必须始终保持您的应用程序可发布。但是,如果您正在进行重大重构或添加复杂的新功能怎么办?版本控制中的分支似乎是这个问题的解决方案。但是,我们强烈认为这是错误的答案。1本章介绍如何使您的应用程序始终保持可发布状态,尽管它处于不断变化的状态。其中一项关键技术是大型应用程序的组件化,因此我们将详细介绍组件化,包括构建和管理具有多个组件的大型项目。

Continuous delivery provides the ability to release new, working versions of your software several times a day. That means you have to keep your application releasable at all times. But what if you are engaged in a major refactoring or adding complex new functionality? Branching in version control might seem to be the solution to this problem. However, we feel strongly that this is the wrong answer.1 This chapter describes how to keep your application releasable at all times, despite being under constant change. One of the key techniques for this is componentization of larger applications, so we will treat componentization, including building and managing large projects with multiple components, at length.

什么是组件?这是软件中的一个可怕的超载术语,因此我们将尝试尽可能清楚地说明我们的意思。当我们谈论组件时,我们指的是应用程序中相当大规模的代码结构,具有定义良好的 API,可能会被换成另一个实现。基于组件的软件系统的特点是代码库被分成离散的部分,这些部分通过与其他组件的定义明确、有限的交互来提供行为。

What is a component? This is a horribly overloaded term in software, so we will try to make it as clear as possible what we mean by it. When we talk about components, we mean a reasonably large-scale code structure within an application, with a well-defined API, that could potentially be swapped out for another implementation. A component-based software system is distinguished by the fact that the codebase is divided into discrete pieces that provide behavior through well-defined, limited interactions with other components.

基于组件的系统的对立面是一个整体系统,在负责不同任务的元素之间没有明确的边界或关注点分离。单体系统通常封装性差,逻辑上独立的结构之间的紧密耦合打破了得墨忒耳法则。语言和技术并不重要——它与 Visual Basic 或 Java 中的 GUI 小部件无关。有些人称组件为“模块”。在 Windows 中,组件通常被打包为 DLL。在 UNIX 中,它可能被打包为一个 SO 文件。在 Java 世界中,它可能是一个 JAR 文件。

The antithesis of a component-based system is a monolithic system with no clear boundaries or separation of concerns between elements responsible for different tasks. Monolithic systems typically have poor encapsulation, and tight coupling between logically independent structures breaks the Law of Demeter. The language and technology are unimportant—it has nothing to do with GUI widgets in Visual Basic or Java. Some people call components “modules.” In Windows, a component is normally packaged as a DLL. In UNIX, it may be packaged as an SO file. In the Java world, it is probably a JAR file.

采用基于组件的设计通常被描述为鼓励重用和良好的架构属性,例如松散耦合。这是事实,但它还有另一个重要的好处:它是大型开发人员团队协作的最有效方式之一。在本章中,我们还描述了如何为基于组件的应用程序创建和管理构建系统。

Employing a component-based design is often described as encouraging reuse and good architectural properties such as loose coupling. This is true, but it also has another important benefit: It is one of the most efficient ways for large teams of developers to collaborate. In this chapter, we also describe how to create and manage build systems for component-based applications.

如果你在做一个小项目,你可能会考虑在阅读下一节后跳过本章(无论项目大小你都应该阅读)。许多项目都可以使用单个版本控制存储库和简单的部署管道。然而,许多项目已经演变成一堆无法维护的代码,因为没有人在成本低廉的时候决定创建分立组件。小项目变成大项目的时间点是流动的,会偷偷摸摸地出现在你身上。一旦项目通过某个门槛,以这种方式更改代码的成本非常高。很少有项目负责人会厚颜无耻地要求他们的团队停止开发足够长的时间,以便将大型应用程序重新构建为组件。弄清楚如何创建和管理组件是我们将在本章探讨的主题。

If you work on a small project, you may be thinking of skipping this chapter after reading the next section (which you should read regardless of the project size). Many projects are fine with a single version control repository and a simple deployment pipeline. However, many projects have evolved into an unmaintainable morass of code because nobody made the decision to create discrete components when it was cheap to do so. The point at which small projects change into larger ones is fluid and will sneak up on you. Once a project passes a certain threshold, it is very expensive to change the code in this way. Few project leaders will have the audacity to ask their team to stop development for long enough to rearchitect a large application into components. Working out how to create and manage components is a topic we will explore in this chapter.

本章中的材料取决于对部署管道的良好理解。如果您需要复习,请参阅第 5 章“部署管道剖析”。在本章中,我们还将描述组件如何与分支交互。到本章结束时,我们将涵盖构建系统中的所有三个自由度:部署管道、分支和组件。

The material in this chapter depends on a good understanding of the deployment pipeline. If you need a refresher, refer to Chapter 5, “Anatomy of the Deployment Pipeline.” In this chapter we will also describe how components interact with branches. By the end of this chapter, we will have covered all three degrees of freedom in a build system: the deployment pipeline, branches, and components.

在大型系统上工作时,同时使用所有这三个维度并不罕见。在这样的系统中,组件形成一系列依赖关系,这些依赖关系又依赖于外部库。每个组件可能有多个发布分支。为这些组件中的每一个找到可以组装成一个甚至可以编译的系统的良好版本是一个极其困难的过程,就像打地鼠游戏一样——我们听说过需要几个月的项目。只有完成此操作后,您才能开始通过部署管道移动系统。

It is not unusual, when working on large systems, to have all three of these dimensions in play at once. In such systems, components form a series of dependencies, which in turn depend on external libraries. Each component may have several release branches. Finding good versions of each of these components that can be assembled into a system which even compiles is an extremely difficult process that can resemble a game of whack-a-mole—we have heard of projects where it takes months. Only once you have done this can you start moving the system through the deployment pipeline.

这本质上是持续集成要解决的根本问题。与往常一样,我们提出的解决方案取决于我们希望您现在熟悉的最佳实践。

This, in essence, is the fundamental problem that continuous integration aims to solve. As usual, the solutions that we propose depend on the best practices that we hope by now are familiar to you.

保持您的应用程序可发布

Keeping Your Application Releasable

持续集成旨在让您高度相信您的应用程序在功能级别运行。部署管道是持续集成的扩展,旨在确保您的软件始终可发布。但这两种做法都依赖于在主线上进行开发的团队。2个

Continuous integration is designed to give you a high level of confidence that your application is working at the functional level. The deployment pipeline, an extension of continuous integration, is designed to ensure that your software is always releasable. But both of these practices depend on teams doing development on mainline.2

在开发过程中,团队不断添加功能,有时需要对架构进行重大更改。在这些活动期间,应用程序不可发布,尽管它仍将通过持续集成的提交阶段。通常,在发布之前,团队将停止开发新功能并进入稳定阶段,在此期间只进行错误修复。当应用程序发布时,在版本控制中创建一个发布分支,并在主干上重新开始新的开发。但是,此过程通常会导致发布间隔数周或数月。持续交付的目的是让应用程序始终处于可发布状态。我们怎样才能做到这一点?

In the course of development, teams are continually adding features, and sometimes need to make major architectural changes. During these activities the application is not releasable, although it will still pass the commit stage of continuous integration. Usually, before release, teams will stop developing new functionality and enter a stabilization phase during which only bugfixing takes place. When the application is released, a release branch is created in version control, and new development begins again on trunk. However, this process generally results in weeks or months between releases. The aim of continuous delivery is for the application to always be in a releasable state. How can we achieve this?

一种方法是在版本控制中创建分支,在工作完成时合并这些分支,以便主线始终是可发布的。我们将在下一章“高级版本控制”中详细研究这种方法。但是,我们认为这种方法不是最优的,因为如果工作发生在分支上,则应用程序不会持续集成。相反,我们提倡每个人都在主线上签到。怎么可能让每个人都在主线上工作,同时仍然让你的应用程序始终处于可发布状态?

One approach is to create branches in version control that are merged when work is complete, so that mainline is always releasable. We examine this approach at length in the next chapter, “Advanced Version Control.” However, we believe that this approach is suboptimal, since the application is not being continuously integrated if work happens on branches. Instead, we advocate that everybody checks in on mainline. How is it possible to have everybody working on mainline, and still keep your application in a releasable state at all times?

为了让您的应用程序在面对变化时保持可发布性,可以采用四种策略:

There are four strategies to employ in order to keep your application releasable in the face of change:

• 在完成之前隐藏新功能。

• Hide new functionality until it is finished.

• 将所有更改作为一系列小更改逐步进行,每个更改都是可发布的。

• Make all changes incrementally as a series of small changes, each of which is releasable.

• 使用抽象分支对代码库进行大规模更改。

• Use branch by abstraction to make large-scale changes to the codebase.

• 使用组件来解耦应用程序中以不同速率变化的部分。

• Use components to decouple parts of your application that change at different rates.

我们将在这里讨论前三种策略。这三种策略应该足以应对小型项目。在较大的项目中,您需要考虑使用组件,我们将在本章的其余部分介绍这些内容。

We’ll discuss the first three strategies here. These three strategies should suffice on small projects. On larger projects, you will need to think about using components, which we cover in the rest of the chapter.

隐藏新功能直到完成

Hide New Functionality Until It Is Finished

持续部署应用程序的一个常见问题是开发一个功能或一组功能可能需要很长时间。如果逐步发布一组功能没有意义,通常很想在版本控制的分支上开始新开发,并在功能准备就绪时集成,以免打断其余部分正在完成的工作一个系统,这可能会阻止它被释放。

One common problem with continuous deployment of applications is that a feature, or a set of features, can take a long time to develop. If it doesn’t make sense to release a set of features incrementally, it is often tempting to start new development on a branch in version control, and integrate when the functionality is ready, so as not to interrupt the work being done on the rest of a system, which might prevent it being released.

一种解决方案是添加新功能,但让用户无法访问它们。例如,考虑一个提供旅游服务的网站。运营该网站的公司希望提供一项新服务:酒店预订。为了做到这一点,我们开始将这个新产品作为一个单独的组件,通过单独的 URI root /hotel 访问。如果需要,该组件仍然可以与系统的其余部分一起部署——只要不允许访问其入口点(这可以通过 Web 服务器软件中的配置设置来实现)。

One solution is to put in the new features, but make them inaccessible to users. For example, consider a website that provides travel services. The company running the site wants to offer a new service: hotel bookings. In order to do so, work starts on this new offering as a separate component, reached through a separate URI root /hotel. This component can still be deployed along with the rest of the system if desired—so long as access is not permitted to its entry point (this could be accomplished by a configuration setting in your web server software).

确保可以运送半成品组件但用户无法访问的另一种方法是通过配置设置打开和关闭对它们的访问。例如,在富客户端应用程序中,您可能有两个菜单——一个具有新功能,一个没有。您将使用配置设置在两个菜单之间切换。这可以通过使用命令行选项,或通过其他部署时或运行时配置来完成(有关配置软件的更多信息,请参阅第 2 章,“配置管理”)。通过运行时配置打开和关闭功能(或将它们换成替代实现)的能力在运行自动化测试时也非常有用。

An alternative way to ensure that semicompleted components can be shipped while not being accessible to users is to turn access to them on and off by means of configuration settings. For example, in a rich client application, you might have two menus—one with the new feature, and one without. You would use a configuration setting to switch between the two menus. This can be done either through the use of command-line options, or through other deploy-time or runtime configuration (see Chapter 2, “Configuration Management” for more on configuring software). The ability to switch features on and off (or swap them out for alternative implementations) through runtime configuration is also very useful when running automated tests.

即使是大型组织也以这种方式开发软件。我们同事工作的一个世界领先的搜索引擎必须修补 Linux 内核,以便它可以接受打开和关闭其软件中各种功能所需的大量命令行参数。这是一个极端的例子,我们不建议保留太多选项——一旦它们达到了目的,就应该小心地删掉它们。可以在代码库中标记配置选项,并使用静态分析作为提交阶段的一部分,以为此目的提供可用配置选项列表。

Even huge organizations develop software this way. One world-leading search engine our colleagues worked at had to patch the Linux kernel so it could accept the large number of command-line arguments required to turn on and off the various bits of functionality in their software. This is an extreme example, and we don’t recommend keeping too many options around—they should be carefully pruned away once they have served their purpose. It is possible to mark up configuration options in the codebase and use static analysis as part of the commit stage to provide a list of available configuration options for this purpose.

将半完成的功能与应用程序的其余部分一起交付是一个很好的做法,因为这意味着您始终在集成和测试整个系统,因为它随时存在。这使得整个应用程序的规划和交付变得更加容易,因为这意味着不需要将依赖项和集成阶段引入项目计划中。它确保新的正在开发的组件从一开始就可以与软件的其余部分一起部署。这也意味着您正在测试您的整个应用程序,包括您的新组件所需的任何新的或修改的服务,以便始终进行回归。

Shipping semicompleted functionality along with the rest of your application is a good practice because it means you’re always integrating and testing the entire system as it exists at any time. This makes planning and delivering the entire application much easier, since it means dependencies and integration phases do not need to be introduced into the project plan. It ensures that the new components being developed are deployable along with the rest of the software from the beginning. It also means that you’re testing your entire application, including any new or modified services required by your new components, for regressions at all times.

以这种方式编写软件需要一定的规划、仔细的架构和严格的开发。然而,即使在添加主要功能集的同时也能够发布软件的新版本的好处通常是值得付出额外努力的。这种替代方案也优于在新功能开发的版本控制中使用分支。

Writing software in this way requires a certain amount of planning, careful architecture, and disciplined development. However, the benefit in terms of being able to release new versions of your software even while adding major feature sets is usually well worth the extra effort. This alternative is also superior to using branching in version control for new feature development.

增量地进行所有更改

Make All Changes Incrementally

上面的故事——逐步将应用程序移动到全新的 UI——只是通用策略的一个特例:逐步进行所有更改。在进行大的更改时,对源代码进行分支并在分支上进行更改通常很诱人。从理论上讲,如果开发人员可以进行大的、高级别的更改来破坏应用程序,然后将所有内容重新连接起来,那么开发人员可以更快地移动。然而,在实践中,连接一切最终成为困难的部分。如果其他团队同时工作,最后的合并可能会很困难——变化越大,就越难。分支的明显理由越大,您就越不应该分支。

The story above—of moving an application to a completely new UI incrementally—is just a particular example of a general strategy: Make all changes incrementally. It is often tempting, when making large changes, to branch the source code and make the change on the branch. The theory is that developers can move faster if they can make large, high-level changes which break the application and then wire everything back in afterwards. However, in practice, the wiring everything up ends up being the hard part. If other teams are working in the meantime, the merge at the end can be hard—and the bigger the change, the harder it will be. The bigger the apparent reason to branch, the more you shouldn’t branch.

即使将大的更改转化为一系列小的、渐进的更改是一项艰巨的工作,但这意味着您正在解决保持应用程序正常运行的问题,从而避免最后的痛苦。这也意味着您可以在需要时随时停下来,避免因重大变革进行到一半而不得不放弃而产生的沉没成本。

Even if turning large changes into a series of small, incremental changes is hard work while you’re doing it, it means you’re solving the problem of keeping the application working as you go along, preventing pain at the end. It also means you can stop at any time if you need to, avoiding the sunk cost involved in getting halfway through a big change and then having to abandon it.

分析在能够将大的变化作为一系列小的变化来进行方面起着重要的作用。在许多方面,进入它的思维过程与用于将需求分解为更小任务的思维过程相同。然后你要做的是将任务变成一组更小的增量更改。这种额外的分析通常可以导致更少的错误和更有针​​对性的更改——当然,如果您逐步进行更改,您可以在进行过程中进行评估并决定如何(以及是否)继续进行。

Analysis plays an important part in being able to make large changes as a series of small changes. In many ways, the thought process that goes into it is the same thought process used to break a requirement down into smaller tasks. What you then do is turn the tasks into a set of even smaller incremental changes. This additional analysis can often lead to fewer mistakes and more targeted changes—and, of course, if you make changes incrementally, you can take stock as you go along and decide how (and indeed whether) to proceed.

但是,有时有些更改很难以增量方式进行。此时,您应该考虑通过抽象进行分支。

However, sometimes there are changes that are too hard to make in an incremental fashion. At this point, you should consider branching by abstraction.

抽象分支

Branch by Abstraction

当您需要对应用程序进行大规模更改时,此模式是分支的替代方法。不是分支,而是在要更改的部分上创建一个抽象层。然后与现有实现并行创建一个新实现,然后在完成时删除原始实现和(可选)抽象层。

This pattern is an alternative to branching when you need to make a large-scale change to an application. Instead of branching, an abstraction layer is created over the piece to be changed. A new implementation is then created in parallel with the existing implementation, and then when it is complete, the original implementation and (optionally) the abstraction layer are removed.

尽管这种模式被我们的同事 Paul Hammant [aE2eP9] 命名为“抽象分支”,但它实际上是使用分支对应用程序进行大规模更改的替代方法。当应用程序的某些部分需要更改而无法通过一系列小的增量步骤实现时,请执行以下操作:

Although this pattern was named “branch by abstraction” by our colleague, Paul Hammant [aE2eP9], it is in fact an alternative to using branching to make a large-scale change to an application. When some part of the application needs a change that cannot be implemented as a series of small, incremental steps, do this:

1. 对需要更改的系统部分创建抽象。

1. Create an abstraction over the part of the system that you need to change.

2. 重构系统的其余部分以使用抽象层。

2. Refactor the rest of the system to use the abstraction layer.

3. 创建一个新的实现,它在完成之前不属于生产代码路径的一部分。

3. Create a new implementation, which is not part of the production code path until complete.

4. 更新您的抽象层以委托给您的新实现。

4. Update your abstraction layer to delegate to your new implementation.

5.删除旧的实现。

5. Remove the old implementation.

6. 如果不再合适,移除抽象层。

6. Remove the abstraction layer if it is no longer appropriate.

抽象分支是使用分支或一步实现复杂更改的替代方法。它允许团队在持续集成中继续开发应用程序,同时还替换它的大部分,所有这些都在主线上。如果代码库的某些部分需要更改,首先要找到这部分的入口点——接缝——然后放入一个抽象层,委托到当前的实现。然后,您可以在开发新实现的同时开发新的实现。使用哪个实现由可以在部署时甚至运行时修改的配置选项决定。

Branch by abstraction is an alternative to using branches or implementing complex changes in one step. It allows teams to continue developing an application in continuous integration while also replacing large chunks of it, all on the mainline. If some part of the codebase needs to be changed, you first find the entry point to this part—a seam—and put in an abstraction layer which delegates to the current implementation. You then develop the new implementation alongside the new one. Which implementation gets used is decided by a configuration option that can be modified at deploy time or even run time.


图片

您可以在非常高的级别通过抽象进行分支,例如换出整个持久层。您也可以在非常低的级别上执行此操作——例如,使用策略模式将一个类替换为另一个类。依赖注入是另一种支持抽象分支的机制。诀窍是找到或创建允许您插入抽象层的接缝。

You can do branch by abstraction at a very high level, such as swapping out an entire persistence layer. You can also do it at a very low level—swapping out a class for another one using the strategy pattern, for example. Dependency injection is another mechanism that enables branch by abstraction. The trick is finding or creating the seams that allow you to insert an abstraction layer.


这也是一个很好的模式,可以用作将使用泥球“模式”的单一代码库转变为更模块化、结构更好的形式的策略的一部分。将要分离的部分代码库作为组件或重写。如果您可以管理这部分代码库的入口点,也许使用 façade 模式,您可以本地化混乱并通过抽象使用分支来保持应用程序使用旧代码运行,同时创建相同的新模块化版本功能。这种策略有时被称为“隐瞒”或“波将金村”[ayTS3J]。

This is also an excellent pattern to use as part of a strategy for turning a monolithic codebase that uses the ball of mud “pattern” into a more modular, better structured form. Take part of the codebase that you want to separate out as a component or rewrite. Provided you can manage the entry points to this part of the codebase, perhaps using the façade pattern, you can localize the mess and use branch by abstraction to keep the application running with the old code while you create a new, modularized version of the same functionality. This strategy is sometimes known as “sweeping it under the rug” or “Potemkin village” [ayTS3J].

通过抽象进行分支的两个最困难的部分是将入口点隔离到相关代码库部分,并管理需要对正在开发的功能进行的任何更改,这可能是错误修复的一部分。但是,与分支相比,这些问题更容易管理。然而,有时很难在您的代码库中找到一个好的接缝,而分支是唯一的解决方案。使用分支使您的代码库达到可以通过抽象执行分支的状态。

The two most difficult parts of branching by abstraction are isolating the entry points to the part of the codebase in question and managing any changes that need to be made to the functionality that is under development, perhaps as part of bug-fixing. However, these problems are considerably easier to manage than they are with branching. Nevertheless, sometimes it is just too hard to find a good seam in your codebase, and branching is the only solution. Use the branch to get your codebase to a state where you can then perform branch by abstraction.

对您的应用程序进行大规模更改,无论是通过抽象分支还是任何其他技术,都可以从全面的自动化验收测试套件中获益匪浅。当您的应用程序的大块发生更改时,单元和组件测试的粗粒度不足以保护您的业务功能。

Making large-scale changes to your application, whether through branching by abstraction or any other technique, benefits enormously from a comprehensive automated acceptance test suite. Unit and component tests are simply not coarse-grained enough to protect your business functionality when big chunks of your application are being changed.

依赖关系

Dependencies

每当一个软件依赖于另一个软件来构建或运行时,就会发生依赖性。除了最微不足道的应用程序之外,任何应用程序都会有一些依赖关系。大多数软件应用程序至少都依赖于它们的主机操作环境。Java 应用程序依赖于提供 Java SE API 实现的 JVM,CLR 上的 .NET 应用程序,Ruby 和 Rails 框架上的 Rails 应用程序,C 标准库上的 C 应用程序,等等。

A dependency occurs whenever one piece of software depends upon another in order to build or run. In any but the most trivial of applications, there will be some dependencies. Most software applications have, at a minimum, a dependency on their host operating environment. Java applications depend on the JVM which provides an implementation of the Java SE API, .NET applications on the CLR, Rails applications on Ruby and the Rails framework, C applications on the C standard library, and so forth.

本章有两个特别有用的区别:组件和库之间的区别,以及构建时和运行时依赖性之间的区别。

There are two distinctions that will be especially useful in this chapter: the distinction between components and libraries, and that between build-time and runtime dependencies.

我们以这种方式区分组件和库:库指的是您的团队无法控制的软件包,而不是选择使用哪个。库通常很少更新。相比之下,组件是您的应用程序所依赖的软件片段,但它们也是由您的团队或您组织中的其他团队开发的。组件通常会经常更新。这种区别很重要,因为在设计构建过程时,在处理组件时要考虑的事情比库要多。例如,您是一步编译整个应用程序,还是在每个组件发生变化时独立编译它?如何管理组件之间的依赖关系,避免循环依赖?

We distinguish between components and libraries in this way: Libraries refer to software packages that your team does not control, other than choosing which to use. Libraries are usually updated rarely. In contrast, components are pieces of software that your application depends upon, but which are also developed by your team, or other teams in your organization. Components are usually updated frequently. This distinction is important because when designing a build process, there are more things to consider when dealing with components than libraries. For example, do you compile your entire application in a single step, or compile each component independently when it changes? How do you manage dependencies between components, avoiding circular dependencies?

构建时依赖性和运行时依赖性之间的区别如下: 编译和链接应用程序时必须存在构建时依赖性(如果需要);应用程序运行时必须存在运行时依赖项,以执行其通常的功能。这种区别很重要,原因有几个。首先,在您的部署管道中,您将使用许多与已部署的应用程序副本无关的软件,例如单元测试框架、验收测试框架、构建脚本框架等。其次,应用程序在运行时使用的库版本可能不同于它在构建时使用的库版本。当然,在 C 和 C++ 中,您的构建时依赖项只是头文件,而在运行时,您需要二进制文件以动态链接库 (DLL) 或共享库 (SO) 的形式存在。但是您也可以在其他编译语言中做类似的事情,例如针对仅包含系统接口的 JAR 进行构建,以及针对包含完整实现的 JAR 运行(例如,在使用 J2EE 应用程序服务器时)。在您的构建系统中也需要考虑这些注意事项。

The distinction between build-time and runtime dependencies is as follows: Build-time dependencies must be present when your application is compiled and linked (if necessary); runtime dependencies must be present when the application runs, performing its usual function. This distinction is important for several reasons. First, in your deployment pipeline you will be using many different pieces of software that are irrelevant to the deployed copy of the application, such as unit test frameworks, acceptance test frameworks, build scripting frameworks, and so forth. Second, the versions of libraries that the application uses at run time can differ from those that it uses at build time. In C and C++, of course, your build-time dependencies are simply header files, while at run time you require a binary to be present in the form of a dynamic-link library (DLL) or shared library (SO). But you can do similar things in other compiled languages too, such as building against a JAR containing just the interfaces for a system, and running against a JAR containing a full implementation (for example, when using a J2EE application server). These considerations need to be taken into account in your build system too.

管理依赖关系可能很困难。我们将从概述库在运行时发生的最常见的依赖性问题开始。

Managing dependencies can be difficult. We’ll start with an overview of the most common dependency problems that occur with libraries at run time.

依赖地狱

Dependency Hell

也许最著名的依赖管理问题被称为“依赖地狱”,有时通俗地称为“DLL 地狱”。当一个应用程序依赖于某物的一个特定版本,但部署了另一个版本,或者什么都没有部署时,就会发生依赖地狱。

Perhaps the most famous problem of dependency management is known as “dependency hell,” sometimes colloquially called “DLL hell.” Dependency hell occurs when an application depends upon one particular version of something, but is deployed with a different version, or with nothing at all.

DLL 地狱是早期版本的 Microsoft Windows 中非常常见的问题。所有共享库,以 DLL 的形式,都存储在一个系统目录 (windows\system32) 中,没有任何版本控制——新版本只会覆盖旧的。除此之外,在 XP 之前的 Windows 版本中,COM 类表是单例的,因此需要特定 COM 对象的应用程序将以首先加载的版本为准。3所有这一切意味着不同的应用程序不可能依赖于 DLL 的不同版本,甚至无法知道在运行时您将获得哪个版本。

DLL hell was a very common problem in earlier versions of Microsoft Windows. All shared libraries, in the form of DLLs, were stored in a system directory (windows\system32) without any versioning—new versions would simply overwrite old ones. Apart from this, in versions of Windows prior to XP the COM class table was a singleton, so applications that required a particular COM object would be given whichever version had been loaded first.3 All this meant that it was impossible for different applications to depend on different versions of a DLL, or even to know which version you would be given at run time.

.NET 框架的引入通过引入程序集的概念解决了 DLL 地狱问题。可以为经过加密签名的程序集指定版本号,以便区分同一库的不同版本,Windows 将它们存储在全局程序集缓存(称为“GAC”)中,即使即使他们有相同的文件名。现在您可以为您的应用程序提供多个不同版本的库。使用 GAC 的优势在于,如果需要推出关键错误或安全修复程序,您可以一次性更新所有使用受影响 DLL 的应用程序。尽管如此,.NET 还支持 DLL 的“xcopy 部署”,因此它们与应用程序保存在同一目录中,而不是在 GAC 中。

The introduction of the .NET framework resolves the DLL hell problem by introducing the concept of assemblies. Assemblies that are cryptographically signed can be given version numbers that allow different versions of the same library to be distinguished, and Windows stores them in a global assembly cache (known as “the GAC”) which can distinguish between different versions of a library even if they have the same filename. Now you can have several different versions of a library available to your applications. The advantage of using the GAC is that, if a critical bug or security fix needs to be pushed out, you can update at a stroke all applications that use the affected DLL. Nevertheless, .NET also supports “xcopy deployment” of DLLs, whereby they are kept in the same directory as the application rather than in the GAC.

Linux 通过使用一个简单的命名约定来避免依赖地狱:它将一个整数附加到全局库目录 (/usr/lib) 中的每个 .so 文件,并使用一个软链接来确定规范的系统范围版本。然后管理员可以轻松更改应用程序使用的版本。如果应用程序依赖于特定版本,它会请求具有相应版本号的文件。当然,拥有规范的系统范围版本的库意味着确保安装的每个应用程序都适用于该版本。这个问题有两个答案:从源代码编译每个应用程序(Gentoo 采用的方法),或者对每个应用程序的二进制文件进行复杂的回归测试(大多数 Linux 发行版的创建者首选)。这确实意味着如果没有复杂的依赖项管理工具,您将无法随意安装依赖于新版本系统库的应用程序的新二进制分发版。幸运的是,这样的工具以 Debian 包管理系统的形式存在——可能是现存最好的依赖管理工具,这也是 Debian 是一个如此可靠的平台以及 Ubuntu 每年可以发布两次稳定版本的主要原因。

Linux avoids dependency hell by using a simple naming convention: It appends an integer to every .so file in the global library directory (/usr/lib), and uses a soft link to determine the canonical system-wide version. It’s then easy for administrators to change which version is to be used by applications. If an application depends on a specific version, it asks for the file with the corresponding version number. Of course having a canonical system-wide version of a library means ensuring that every application installed works with that version. There are two answers to this problem: compiling every application from source (the approach taken by Gentoo), or doing sophisticated regression testing of every application’s binaries (preferred by most creators of Linux distributions). This does mean that you can’t install new binary distributions of an application that depend on a new version of a system library at will without a sophisticated dependency management tool. Fortunately, such a tool exists in the form of the Debian package management system—possibly the finest dependency management tool in existence and the primary reason why Debian is such a solid platform and why Ubuntu can produce stable releases twice a year.

对操作系统范围的依赖性问题的一个简单回答是明智地应用静态编译。这意味着对您的应用程序最关键的依赖项在编译时聚合到一个程序集中,因此运行时依赖项很少。然而,虽然这使得部署更简单,但它也有一些缺点。除了创建大型二进制文件外,它还将由此创建的二进制文件与特定版本的操作系统紧密耦合,并且无法通过操作系统更新修复错误或安全漏洞。因此通常不推荐静态编译。

A simple answer to the problem of OS-wide dependencies is the judicious application of static compilation. This means that the dependencies that are most critical to your application are aggregated into a single assembly at compile time, so that there are few runtime dependencies. However, while this makes for simpler deployments, it has some drawbacks. As well as creating large binaries, it also tightly couples the binaries thus created to a particular version of the operating system, and makes it impossible to fix bugs or security holes through operating system updates. Thus static compilation is not usually to be recommended.

对于动态语言,等效的方法是随应用程序依赖的任何框架或库一起发布。Rails 采用这种方法,允许整个 Rails 框架与使用它的应用程序一起发布。这意味着您可以同时运行多个 Rails 应用程序,每个应用程序使用不同版本的框架。

For dynamic languages, the equivalent approach is to ship any frameworks or libraries that the application depends upon along with it. Rails takes this approach, allowing the whole Rails framework to be shipped along with applications that use it. This means that you can have multiple Rails applications running simultaneously, each using different versions of the framework.

由于其类加载器的设计,Java 在运行时依赖性方面面临着一个特别严重的问题。最初的设计阻止了一个类的多个版本在同一个 JVM 中可用。这个限制已经以 OSGi 框架的形式被克服,它提供多版本类加载以及热部署和自动更新。如果不使用 OSGi,限制仍然存在,这意味着必须在构建时仔细管理依赖项。一个常见但不愉快的场景是应用程序依赖于两个库(在本例中为 JAR),每个库都依赖于相同的底层库(例如,日志库)但版本不同。该应用程序可能会编译,但它几乎肯定会在运行时失败,或者出现ClassNotFound异常(如果所需的方法或类不存在)或有细微的错误。这个问题被称为菱形依赖问题。

Java faces a particularly severe problem with runtime dependencies due to the design of its classloader. The original design prevented more than one version of a class being available in the same JVM. This restriction has been overcome in the form of the OSGi framework, which provides multiversion class loading as well as hot deployment and autoupdating. Without the use of OSGi the restriction remains, meaning that dependencies have to be managed carefully at build time. A common but unpleasant scenario is an application depending on two libraries (JARs in this case), each of which depends on the same underlying library (for example, a logging library) but a different version. The application will probably compile, but it will almost certainly fail at run time, with either a ClassNotFound exception (if the required method or class is not present) or with subtle bugs. This problem is known as the diamond dependency problem.

我们将在本章后面讨论菱形依赖问题的解决方案和另一个病态案例——循环依赖。

We will discuss the solution to the diamond dependency problem and another pathological case—circular dependencies—later in this chapter.

管理图书馆

Managing Libraries

在软件项目中有两种合理的管理库的方法。一种是将它们签入版本控制。另一种方法是声明它们并使用 Maven 或 Ivy 等工具从 Internet 存储库或(最好)您组织自己的工件存储库下载库。您需要强制执行的关键约束是构建是可重复的——也就是说,如果我从版本控制中检出项目并运行自动构建,我可以保证我将获得与项目中其他人所做的完全相同的二进制文件,并且三个月后,当我必须调试运行旧版本软件的用户报告的问题时,我可以创建完全相同的二进制文件。

There are two reasonable ways of managing libraries in software projects. One is to check them into version control. Another is to declare them and use a tool like Maven or Ivy to download libraries from Internet repositories or (preferably) your organization’s own artifact repository. The key constraint you need to enforce is that builds are repeatable—that is, if I check out the project from version control and run the automated build, I can guarantee I will get exactly the same binaries that everybody else on the project does, and that I can create exactly the same binaries three months from now when I have to debug a problem reported by a user running an old version of my software.

将库签入版本控制是最简单的解决方案,适用于小型项目。传统上,在项目的根目录中创建一个 lib 目录以将库放入其中。我们建议再添加三个子目录:build、test 和 run——用于构建时、测试时和运行时依赖项。我们还建议对包含其版本号的库使用命名约定。所以不要只将 nunit.dll 检查到您的 lib 目录中——检查 nunit-2.5.5.dll。这样,您就可以确切地知道您使用的是哪个版本,并且很容易确定您是否了解最新和最好的所有内容。这种方法的好处是构建应用程序所需的一切都在版本控制中——一旦您在本地签出项目存储库,

Checking libraries into version control is the simplest solution, and will work fine for small projects. Traditionally, a lib directory is created in your project’s root to put libraries into. We suggest adding three further subdirectories: build, test, and run—for build-time, test-time, and runtime dependencies. We also suggest using a naming convention for libraries that includes their version number. So don’t just check nunit.dll into your lib directory—check in nunit-2.5.5.dll. That way, you know exactly which versions you’re using, and it’s easy to determine whether or not you’re up-to-date with the latest and greatest of everything. The benefit of this approach is that everything you need to build your application is in version control—once you have a local check-out of the project repository, you know you can repeatably build the same packages that everybody else has.


图片

检查整个工具链是个好主意,因为这代表了项目的构建时依赖性。但是,您应该将它签入与项目其余部分不同的存储库,因为您的工具链存储库很容易变得非常大。您应该防止您的项目存储库变得太大,以至于执行常见的存储库操作需要几秒钟以上的时间,例如查看本地更改和将小更改提交到中央存储库。另一种选择是将您的工具链保存在共享的网络附加存储中。

It’s a good idea to check in your entire toolchain, since this represents a build-time dependency of your project. However, you should check it into a different repository from the rest of your project, because your toolchain repository can easily become very large. You should prevent your project repository from becoming so big that it takes more than a few seconds to perform common repository operations, such as seeing local changes and committing small changes to the central repository. Another alternative is to keep your toolchain on shared, network-attached storage.


签入图书馆有几个问题。首先,随着时间的推移,您签入的库存储库可能会变得庞大而笨拙,并且您可能很难知道您的应用程序仍在使用这些库中的哪些。如果您的项目必须与同一平台上的其他项目一起运行,则会出现另一个问题。一些平台可以处理使用同一个库的多个版本的项目,而其他平台(例如没有 OSGi 的 JVM,或 Ruby Gems)不允许使用同一个库的多个版本。在这种情况下,您需要小心使用其他项目使用的相同版本的库。手动管理跨项目的传递依赖关系很快变得很痛苦。

There are a couple of problems with checking in libraries. First, over time, your checked-in library repository may become large and crufty, and it may become hard to know which of these libraries are still being used by your application. Another problem crops up if your project must run with other projects on the same platform. Some platforms can handle projects using multiple versions of the same library, while others (for example the JVM without OSGi, or Ruby Gems) do not allow multiple versions of the same library to be used. In this case, you need to be careful to use the same versions of libraries that other projects use. Manually managing transitive dependencies across projects rapidly becomes painful.

Maven 和 Ivy 提供了一种自动化的依赖管理方法,它允许您在项目配置中准确声明所需的库版本。然后这些工具会下载您需要的库的适当版本,传递解决对其他项目的依赖(如果适用)并确保项目依赖图中没有不一致,例如两个组件需要某些公共库的相互不兼容版本。这些工具会将项目所需的库缓存在本地计算机上,因此尽管首次在新计算机上运行项目可能需要很长时间才能构建,但进一步构建不会比将库签入版本控制慢. Maven 的问题在于,为了享受可重复的构建,您必须将其配置为使用其插件的特定版本,并确保您指定每个项目依赖项的确切版本。本章稍后会详细介绍 Maven 的依赖管理。

An automated approach to dependency management is provided by Maven and Ivy, which allow you to declare exactly which versions of your libraries you need as part of your project’s configuration. The tools then download the appropriate versions of the libraries you require, transitively resolving dependencies on other projects (if applicable) and ensuring that there are no inconsistencies in the project dependency graph, such as two components requiring mutually incompatible versions of some common library. These tools will cache the libraries your project needs on your local machine, so although the project can take a long time to build when you first run it on a new machine, further builds are no slower than if you had the libraries checked into version control. The problem with Maven is that in order to enjoy repeatable builds, you must configure it to use specific versions of its plugins, and ensure that you specify the exact versions of each of your project’s dependencies. There is more on dependency management with Maven later on in this chapter.

使用依赖项管理工具时的另一个重要实践是管理您自己的工件存储库。开源工件存储库包括 Artifactory 和 Nexus。这有助于确保构建可重复,并通过控制每个库的哪些版本可用于组织内的项目来防止依赖地狱。这种做法还可以更轻松地审核您的库并防止违反法律约束,例如在 BSD 许可的软件中使用 GPL 许可的库。

Another important practice when using dependency management tools is to manage your own artifact repository. Open source artifact repositories include Artifactory and Nexus. This helps ensure that builds are repeatable and prevents dependency hell by controlling which versions of each library are available to projects within your organization. This practice also makes it much easier to audit your libraries and prevent violations of legal constraints, such as using GPL-licensed libraries in BSD-licensed software.

如果 Maven 和 Ivy 不合适,也可以通过一个简单的属性文件来滚动您自己的声明式依赖管理系统,该文件指定您的项目所依赖的库以及这些库的版本。然后您可以编写一个脚本,从中下载这些库的正确版本您组织的工件存储库——它可以像一个带有简单 Web 服务的备份文件系统一样简单。当然,如果您需要处理更复杂的问题,例如解析传递依赖,您将需要更强大的解决方案。

If Maven and Ivy are unsuitable, it is also possible to roll your own declarative dependency management system by having a simple properties file which specifies the libraries your projects depend on and the versions of these libraries. You can then write a script which downloads the correct versions of these libraries from your organization’s artifact repository—which can be as simple as a backed-up filesystem fronted with a simple web service. Of course you will need a more powerful solution if you need to handle more complex problems such as resolving transitive dependencies.

组件

Components

几乎所有现代软件系统都由一组组件组成。这些组件可能是 DLL、JAR 文件、OSGi 包、Perl 模块或其他东西。组件在软件行业的历史比较悠久。然而,弄清楚如何将它们组装成可部署的工件,以及如何实现考虑组件之间交互的部署管道,是一项艰巨的任务。这种复杂性的结果通常通过花费数小时来组装可部署、可测试的应用程序的构建来证明。

Almost all modern software systems consist of a collection of components. These components may be DLLs, JAR files, OSGi bundles, Perl modules, or something else. Components have a relatively long history in the software industry. However, working out how to assemble them into deployable artifacts, and how to implement a deployment pipeline that takes account of the interactions between components, is a nontrivial task. The results of this complexity are often demonstrated by builds that take many hours to assemble a deployable, testable application.

大多数应用程序都是从单个组件开始的。有些从两个或三个开始(例如,客户端-服务器应用程序)。那么为什么要将代码库拆分成组件,又该如何管理它们之间的关系呢?除非这些关系得到有效管理,否则可能会损害将它们用作持续集成系统的一部分的能力。

Most applications start off as a single component. Some start off as two or three (for example, a client-server application). So why should a codebase be split into components, and how should the relationships between them be managed? Unless these relationships are managed effectively, it can compromise the ability to use them as part of a continuous integration system.

如何将代码库划分为组件

How to Divide a Codebase into Components

软件中“组件”的概念是大多数人在看到它时都会认出的概念,但它有许多不同的、通常是模糊的定义。出于本章介绍的目的,我们已经粗略地定义了组件的含义,但是大多数人会同意组件的其他一些属性。一个相当没有争议的陈述可能看起来像这样:“一个组件是可重用的,可以用实现相同 API 的其他东西替换,可以独立部署,并且封装了系统的一些连贯的行为和职责集。”

The idea of a “component” in software is one that most people will recognize when they see it, but that has many different, often woolly, definitions. We have already loosely defined what we mean by a component for the purposes of this chapter in the introduction, but there a few other properties of components that most people would agree upon. A fairly uncontroversial statement might look like this: “A component is reusable, replaceable with something else that implements the same API, independently deployable, and encapsulates some coherent set of behaviors and responsibilities of the system.”

显然,原则上,一个类可以具有这些特征——但通常情况并非如此。组件可独立部署的要求意味着类通​​常不符合条件。没有什么可以阻止我们打包单个类以便部署它,但在大多数情况下,与打包相关的开销在这个细节级别上没有意义。此外,班级通常以集群形式工作,班级的小组紧密合作以提供有用的行为,并且相对而言,与他们的密切合作者更紧密地耦合在一起。

Clearly a single class could, in principle, have these characteristics—but generally this is not the case. The requirement for components to be independently deployable means that classes don’t usually qualify. There is nothing to prevent us packaging a single class so it can be deployed, but in most cases the overhead associated with packaging doesn’t make sense at this level of detail. In addition, classes usually work in clusters, with small groups of classes working closely together to deliver useful behavior and being, relatively speaking, more tightly coupled to their close collaborators.

由此我们可以假设构成组件的内容有一些下限。在将组件视为应用程序的独立部分之前,它应该具有一定程度的复杂性。那么上限呢?我们将系统划分为组件的目的是提高我们作为一个团队的效率。组件使软件开发过程更高效的原因有以下几个:

From this we can assume that there is some lower bound for what constitutes a component. A component should have a certain level of complexity before it can be considered an independent piece of your application. So what of an upper bound? Our aim in dividing a system into components is to increase our efficiency as a team. There are several reasons why components make the software development process more efficient:

1. 他们将问题分成更小、更具表现力的块。

1. They divide the problem into smaller and more expressive chunks.

2、组件往往代表着系统不同部分变化率的差异,具有不同的生命周期。

2. Components often represent differences in the rates of change of different parts of the system, and have different lifecycles.

3. 他们鼓励我们设计和维护具有清晰职责划分的软件,这反过来限制了变更的影响,并使理解和更改代码库变得更加容易。

3. They encourage us to design and maintain software with clear delineation of responsibilities, which in turn limits the impact of change, and makes understanding and changing the codebase easier.

4. 他们可以为我们提供额外的自由度来优化我们的构建和部署过程。

4. They can provide us with additional degrees of freedom in optimizing our build and deployment process.

大多数组件的一个重要特征是它们公开某种形式的 API。该 API 的技术基础可以提供不同的方式:动态链接、静态链接、Web 服务、文件交换、消息交换等。API 的性质可能不同,但重要的是它代表了与外部合作者的信息交换——因此,至关重要的是,该组件与这些合作者的耦合程度。即使组件的接口是文件格式或消息模式,它仍然表示信息耦合,这反过来又需要考虑组件之间的依赖关系。

A significant feature of most components is that they expose an API of some form. The technical basis of this API could be provided differently: dynamic linking, static linking, a web service, file exchange, message exchange, and so forth. The nature of the API may differ, but it is important in that it represents an exchange of information with external collaborators—and so, vitally, the degree to which that component is coupled to these collaborators. Even when the interface to the component is a file format or a message schema, it still represents an informational coupling which will, in turn, require consideration of dependencies between components.

当组件在构建和部署过程中被分离并视为独立单元时,正是组件之间在接口和行为方面的耦合程度增加了复杂性。

It is the degree of coupling between components, both in terms of interface and behavior, that adds complexity when they are separated and treated as independent units in a build and deployment process.

以下是将组件从代码库中分离出来的一些充分理由:

Here are some good reasons to separate out a component from your codebase:

1. 您的部分代码库需要独立部署(例如,服务器或富客户端)。

1. Part of your codebase needs to be deployed independently (for example, a server or a rich client).

2. 你想把一个单一的代码库变成一个核心和一组插件,也许是用替代实现替换你系统的某些部分,或者提供用户可扩展性。

2. You want to turn a monolithic codebase into a core and a set of plugins, perhaps to replace some part of your system with an alternative implementation, or to provide user extensibility.

3. 组件向另一个系统(例如提供 API 的框架或服务)提供接口。

3. The component provides an interface to another system (for example a framework or a service which provides an API).

4.编译链接代码耗时过长。

4. It takes too long to compile and link the code.

5.在开发环境中打开项目时间过长。

5. It takes too long to open the project in the development environment.

6. 你的代码库太大,一个团队无法处理。

6. Your codebase is too large to be worked on by a single team.

尽管此列表中的最后三个条目听起来相当主观,但它们是梳理组件的完全正当理由。最后一点尤其重要。当团队由大约 10 个人组成时,他们对代码库的特定部分了如指掌,无论是功能组件还是其他边界。如果您需要十个人以上的人员以您需要的速度进行开发,一种非常有效的方法是将您的系统划分为松散耦合的组件,并划分团队。

Although the last three entries in this list may sound rather subjective, they are perfectly valid reasons to tease out components. The final point is especially critical. Teams work best when they comprise around ten people who understand a particular part of the codebase inside out, whether it’s a functional component or some other boundary. If you need more than ten people to develop at the speed you need to, one very effective way to do this is to divide your system into loosely coupled components, and divide the teams too.

我们建议让团队负责单个组件。这是因为在大多数情况下,需求不会沿着组件边界划分。根据我们的经验,人们端到端地开发功能的跨职能团队效率要高得多。尽管每个组件一个团队似乎更有效率,但实际上并非如此。

We do not recommend making teams responsible for individual components. This is because in most cases, requirements don’t divide along component boundaries. In our experience, cross-functional teams in which people develop features end-to-end are much more effective. Although one team per component may seem more efficient, this is not in fact the case.

首先,通常很难单独为单个组件编写和测试需求,因为通常实现一项功能会涉及多个组件。如果按组件对团队进行分组,则需要两个或更多团队协作才能完成一项功能,从而自动增加大量不必要的沟通成本。此外,以组件为中心的团队中的人员倾向于形成孤岛并在本地进行优化,从而失去判断什么最符合项目整体利益的能力。

First, it is often hard to write and test requirements for a single component in isolation, since usually implementing a piece of functionality will touch more than one component. If you group teams by component, you thus require two or more teams to collaborate to complete a feature, automatically adding a large and unnecessary communication cost. Furthermore, people in component-centered teams tend to form silos and optimize locally, losing their ability to judge what is in the best interest of the project as a whole.

最好将团队分开,这样每个团队都负责一个故事流(也许所有故事都有一个共同的主题),并触及他们完成工作所需的任何组件。具有实施业务级功能的授权的团队,以及可以自由更改他们需要的任何组件的团队,效率要高得多。按职能领域而不是按组件组织团队,确保每个人都有权更改代码库的任何部分,定期在团队之间轮换人员,并确保团队之间有良好的沟通。

It is better to split teams up so that each team takes on one stream of stories (perhaps all with a common theme), and touches whatever components they need to in order to get their work done. Teams with a mandate to implement a businesslevel feature, and the freedom to change any component that they need to, are much more efficient. Organize teams by functional area rather than by component, ensure that everybody has the right to change any part of the codebase, rotate people between teams regularly, and ensure that there is good communication between teams.

这种方法还有一个好处,就是让所有组件一起工作是每个人的责任,而不仅仅是集成团队的责任。每个组件都有一个团队的更严重的危险之一是,应用程序作为一个整体要到项目结束才能工作,因为没有人有动力集成这些组件。

This approach also has the benefit that making all the components work together is everybody’s responsibility, not just that of the integration team. One of the more serious dangers of having a team per component is that the application as a whole won’t work until the end of the project because nobody has the incentive to integrate the components.

上面列表中的第四和第五个原因通常是模块化不足的不良设计的症状。一个设计良好的代码库遵循“不要重复自己”(DRY)原则,并且由遵守 Demeter 法则的封装良好的对象组成,通常更高效、更容易处理,也更容易拆分成组件当需要出现时。但是,过度激进的组件化也可能导致构建过程缓慢。这似乎在 .NET 世界中特别普遍,有些人喜欢在他们的解决方案中创建大量项目,没有充分的理由。这样做总是会导致编译速度变慢。

Reasons four and five in the list above are often symptoms of a poor design which is insufficiently modular. A well-designed codebase which follows the “Don’t repeat yourself” (DRY) principle, and which is composed of wellencapsulated objects that obey the Law of Demeter, is usually more efficient, easier to work on, and easier to split into components when the need arises. However, a slow build process can also be caused by overaggressive componentization. This seems to be particularly prevalent in the .NET world, where some people like to create a large number of projects within their solution, for no good reason. Doing so invariably causes compilation to slow to a crawl.

除了上面讨论的良好设计考虑因素之外,关于如何将应用程序组织为组件集合并没有硬性规定。然而,有两个常见的失败:“无处不在的组件”和“一个组件统治一切”。经验表明,这两个极端都不合适,但无论经验水平如何,判断边界在哪里仍然是开发人员和架构师的判断要求。这是使软件设计既是一门工程学科又成为一门艺术、工艺和社会科学的众多因素之一。

There are no hard and fast rules about how to organize your application as a collection of components, apart from the considerations of good design discussed above. There are, however, two common failings: “components everywhere” and “the one component to rule them all.” Experience shows that neither extreme is appropriate, but gauging where the boundaries are remains a judgment call for developers and architects of whatever level of experience. This is one of the many factors that makes software design an art, craft, and social science as much as it is an engineering discipline.

最后,值得注意的是康威定律,该定律指出“设计系统的组织……被迫生产出这些组织的通信结构副本的设计。”4因此,例如,开发人员仅通过电子邮件进行交流的开源项目往往非常模块化,接口很少。由一个位于同一地点的小型团队开发的产品将倾向于紧密耦合而不是模块化。5注意你如何组建你的开发团队——它会影响你的应用程序的架构。

Finally, it’s worth noting Conway’s Law, which states that “organizations which design systems... are constrained to produce designs which are copies of the communication structures of these organizations.”4 So, for example, open source projects where developers communicate only by email tend to be very modular with few interfaces. A product developed by a small, colocated team will tend to be tightly coupled and not modular.5 Be careful of how you set up your development team—it will affect the architecture of your application.

如果您的代码库已经很大且单一,开始将其分解为组件的一种方法是使用抽象分支,如本章前面所述。

If your codebase is already large and monolithic, one way to start decomposing it into components is to use branching by abstraction, as described earlier in this chapter.

流水线组件

Pipelining Components

即使您的应用程序由多个组件组成,也不意味着您需要为每个组件单独构建。事实上,最简单的方法,也是一种可以扩展到惊人程度的方法,就是为您的整个应用程序使用一个单一的管道。每次提交更改时,都会构建和测试所有内容。在大多数情况下,我们建议将您的系统构建为一个单一的实体,直到获得反馈的过程变得太慢为止。正如我们所说,如果你遵循本书中的建议,你可能会发现你可以用这种方式构建出奇大而复杂的系统。这种方法的优点是很容易跟踪哪一行代码破坏了构建。

Even when your application is comprised of several components, it doesn’t mean that you need to have a separate build for each one. Indeed the simplest approach, and one that scales up to a surprising degree, is to have a single pipeline for your entire application. Every time a change is committed, everything is built and tested. In most cases, we would recommend building your system as a single entity until the process of getting feedback becomes too slow. As we have said, if you follow our advice in this book, you will likely find that you can build surprisingly large and complex systems this way. This approach has the advantage that it is very easy to trace which line of code broke the build.

然而,实际上有很多情况可以从将系统拆分为多个不同的管道中获益。以下是一些使用单独管道的情况示例:

However, realistically there are many circumstances that benefit from splitting your system into several different pipelines. Here are a few examples of circumstances where it makes sense to have separate pipelines:

• 具有不同生命周期的应用程序部分(也许您构建自己的操作系统内核版本作为应用程序的一部分,但您只需要每隔几周执行一次)。

• Parts of your application that have a different lifecycle (perhaps you build your own version of an OS kernel as part of your application, but you only need to do this once every few weeks).

• 由不同(可能是分布式)团队处理的应用程序的功能独立区域可能具有特定于这些团队的组件。

• Functionally separate areas of your application that are worked on by different (perhaps distributed) teams may have components specific to those teams.

• 使用不同技术或构建过程的组件。

• Components that use different technologies or build processes.

• 由多个其他项目使用的共享组件。

• Shared components that are used by several other projects.

• 相对稳定且不经常更改的组件。

• Components that are relatively stable and do not change frequently.

• 构建应用程序花费的时间太长,为每个组件创建构建会更快(但请注意,这成为现实的时间比大多数人想象的要晚得多)。

• It takes too long to build your application, and creating builds for each component will be faster (but beware, the point at which this becomes true is much later than most people think).

从构建和部署过程的角度来看,重要的是管理基于组件的构建总是有一些额外的开销。为了将单个构建变成多个构建,您需要为每个创建一个构建系统。这意味着每个单独的部署管道都有一个新的目录结构和构建文件,每个部署管道都应遵循与整个系统相同的模式。这意味着每个构建的目录结构应该包括单元测试、验收测试、它所依赖的库、构建脚本、配置信息以及您通常会放入项目版本控制中的任何其他内容。每个组件或组件集的构建应该有自己的管道来证明它适合发布。该管道将​​执行以下步骤:

The important thing from the perspective of the build and deployment process is that there is always some additional overhead to the management of a component-based build. In order to turn a single build into several, you need to create a build system for each. This means a new directory structure and build file for each separate deployment pipeline, each of which should follow the same pattern as that for an entire system. That means the directory structure for each build should include unit tests, acceptance tests, the libraries it depends on, build scripts, configuration information, and anything else you would normally put into version control for a project. The build for each component or set of components should have its own pipeline to prove that it is fit for release. This pipeline will perform the following steps:

• 如有必要,编译代码。

• Compile the code, if necessary.

• 组装一个或多个能够部署到任何环境的二进制文件。

• Assemble one or more binaries that are capable of deployment to any environment.

• 运行单元测试。

• Run unit tests.

• 运行验收测试。

• Run acceptance tests.

• 酌情支持手动测试。

• Support manual testing, where appropriate.

对于整个系统,该过程可确保您尽早获得反馈,断言每个更改的可行性。

The process, as for a whole system, ensures that you get feedback as early as possible, asserting the viability of each change.

一旦二进制文件通过了它们自己的迷你发布过程,它们就可以升级为集成构建(下一节将详细介绍)。您需要将二进制文件发布到工件存储库,以及一些元数据以标识用于创建二进制文件的源版本。一个现代的 CI 服务器应该能够为你做这件事,尽管如果你想自己做,它可以像将二进制文件存储在一个目录中一样简单,该目录具有生成它的管道标签的名称。另一种选择是使用 Artifactory、Nexus 或其他一些工件存储库软件。

Once the binaries have passed through their own mini release process, they are ready for promotion to an integration build (more on this in the next section). You will need to publish the binaries to an artifact repository, along with some metadata to identify the version of the source that was used to create the binary. A modern CI server should be able to do this for you, although if you want to do it yourself it can be as simple as storing the binaries in a directory with the name of the pipeline label that produced it. Another alternative is to use Artifactory, Nexus, or some other artifact repository software.

请注意,我们强调并不是说您应该为每个 DLL 或 JAR 创建一个管道。这就是为什么我们一直小心翼翼地在上面反复说“组件或组件集”。一个组件可能包含多个二进制文件。一般来说,指导原则应该是尽量减少您操作的构建数量。一比二好,二比三好,依此类推。在转向并行流水线方法之前,请继续优化构建并尽可能长时间地提高构建效率。

Please note that we are emphatically not saying that you should create a pipeline for every DLL or JAR. That’s why we’ve been careful to say “component or set of components” repeatedly above. A component may consist of several binaries. In general, the guiding principle should be to minimize the number of builds that you operate. One is better than two, two better than three, and so on. Keep optimizing the build and making it more efficient for as long as possible before moving to a parallel pipeline approach.

集成管道

The Integration Pipeline

图 13.1 集成管道

Figure 13.1 Integration pipeline

图片

集成管道将构成系统的每个组件的二进制输出作为起点。集成管道的第一阶段应该通过组合适当的二进制文件集合来创建一个适合部署的包(或多个包)。第二阶段应该将生成的应用程序部署到类似生产的环境中,并对它运行冒烟测试,以尽早发现任何基本的集成问题。如果这个阶段是成功的,然后流水线应该进入常规的验收测试阶段,以通常的方式运行整个应用程序的验收测试。然后遵循适用于应用程序的正常阶段顺序,如图 13.1所示。

The integration pipeline takes as its starting point the binary output from each of the components that comprise your system. The first stage of the integration pipeline should create a package (or packages) suitable for deployment by composing the appropriate collections of binaries. The second stage should deploy the resulting application to a production-like environment and run smoke tests against it to give early indication of any basic integration problems. If this stage is successful, then the pipeline should move on to a conventional acceptance test stage, running whole application acceptance tests in the usual way. Then follows the normal sequence of stages appropriate to the application, as shown in Figure 13.1.

在创建集成管道时,需要牢记部署管道的两个一般原则:快速反馈的需要以及为所有相关方提供构建状态可见性的需要。长管道或管道链可能会影响反馈。如果您发现自己处于这种情况并且您有足够的硬件,一种解决方案是在创建二进制文件并通过单元测试后立即触发下游管道。

There are two general principles of deployment pipelines to bear in mind when creating an integration pipeline: the need for fast feedback and the need to provide visibility into the status of the build for all interested parties. Feedback can be compromised by long pipelines, or chains of pipelines. If you find yourself in this situation and you have sufficient hardware, one solution is to trigger downstream pipelines as soon as binaries are created and the unit tests pass.

在可见性方面,如果集成管道的任何阶段失败,应该可以准确地看到它失败的原因。这意味着从集成构建回溯到为其做出贡献的每个组件的版本的能力是关键。如果您要能够发现导致中断的源代码更改,那么维护这些关系是必不可少的。现代 CI 工具应该能够为您做到这一点,所以如果您的工具没有,请找一个可以的。追踪集成管道失败的原因应该不会超过几秒钟。

In terms of visibility, if any stage of the integration pipeline fails, it should be possible to see exactly why it broke. This means that the ability to trace back from an integration build to the versions of each component that contributed to it is key. The maintenance of these relationships is essential if you are to be able to discover the changes in source code responsible for the breakage. Modern CI tools should be able to do this for you, so if yours doesn’t, find one that does. It shouldn’t take more than a few seconds to track down the cause of an integration pipeline failure.

同样,并非每个单独组件的“绿色”构建在与构成整个应用程序的其他组件组合时实际上都是好的。因此,处理组件的团队应该能够了解其组件的哪些版本实际上最终进入了绿色集成管道(因此可以被认为有利于集成)。事实上,只有这些版本的组件才是真正的“绿色”。集成管道形成每个单独组件管道的扩展。因此,可见性在两个方向上都很重要。

It also follows that not every “green” build of an individual component will actually be good when combined with the other components that make up the application as a whole. Therefore, the team working on the components should have visibility into which versions of their component actually ended up in a green integration pipeline (and can thus be considered good for integration). Only these versions of the components are in fact really “green.” The integration pipeline forms an extension of each individual component’s pipeline. So visibility is important in both directions.

如果几个组件在集成管道的一次运行和下一次运行之间发生变化,它很可能会花费大部分时间被破坏。这是有问题的,因为它使得查找哪个更改破坏了您的应用程序变得更加困难,因为自上一个应用程序的良好版本以来会有太多更改。

If several components change between one run of the integration pipeline and the next, it is probable that it will spend much of its time broken. This is problematic because it makes it more difficult to find which change broke your application, as there will be so many changes since the last good version of the application.

有几种不同的技术可以解决这个问题,我们将在本章的其余部分进行探讨。最简单的方法是构建组件的良好版本的每一个可能的组合。如果您的组件不经常更改,或者您在构建网格上有足够的计算能力,则可以这样做。这是最好的方法,因为它不涉及任何人为干预或巧妙的算法,而且与执行取证的人类相比,计算能力最终是便宜的。因此,如果可以,请执行此操作。

There are several different techniques to solve this problem, which we’ll explore in the rest of this chapter. The simplest approach is to build every single possible combination of the good versions of your components. If your components don’t change that often, or you have sufficient computing power on your build grid, you can do this. This is the best approach because it doesn’t involve any human intervention or clever algorithms, and computing power is ultimately cheap compared to humans performing forensics. So if you can, do this.

下一个最佳方法是构建尽可能多的应用程序版本。您可以使用一个相对简单的调度算法来做到这一点,该算法采用每个组件的最新版本并尽可能频繁地组装您的应用程序。如果此操作足够快,您可以针对应用程序的每个版本运行一个简短的冒烟测试套件。如果您的冒烟测试需要一段时间才能运行,它们可能只会针对您的应用程序的每三个版本运行一次。

The next best approach is to build as many versions of your application as you can. You can do this with a relatively simple scheduling algorithm that takes the latest version of every component and assembles your application as frequently as it can. If this operation is sufficiently fast, you can run a short smoke test suite against each version of your app. If your smoke tests take a while to run, they might only end up running against every third version of your application.

然后,您可以通过一些手动方式来选择一组给定的组件版本,并说“组装这些组件并使用它们创建我的集成管道的实例”,如某些 CI 工具所提供的那样。

You could then have some manual way to select a given set of versions of your components and say, “Assemble these and create an instance of my integration pipeline with them,” as provided by some CI tools.

管理依赖图

Managing Dependency Graphs

它对版本依赖关系至关重要,包括库和组件。如果您无法对依赖项进行版本控制,您将无法重现构建。这意味着,除其他外,当您的应用程序由于依赖项的更改而中断时,您将无法回溯并找到导致它崩溃的更改,或者找到库的最后一个“好”版本。

It is vital to version dependencies, including libraries and components. If you fail to version dependencies, you won’t be able to reproduce builds. That means, among other things, that when your application breaks due to a change in a dependency, you won’t be able to trace back and find the change that broke it, or find the last “good” version of the library.

在上一节中,我们讨论了一组组件,每个组件都有自己的管道,它们被送入一个集成管道,该管道组装应用程序并在最终应用程序上运行自动和手动测试。然而,事情通常并不这么简单:组件可以依赖于其他组件,包括第三方库。如果你画一个组件之间的依赖关系图,它应该是一个有向无环图(DAG)。如果不是这种情况(特别是,如果您的图表有循环),您就会遇到病态依赖问题,我们将很快解决这个问题。

In the previous section, we discussed a set of components, each with their own pipeline, feeding into an integration pipeline which assembles the application and runs automated and manual tests on the final application. However, things are often not quite this simple: Components can have dependencies on other components, including third-party libraries. If you draw a diagram of the dependencies between components, it should be a directed acyclic graph (DAG). If this is not the case (and in particular, if your graph has cycles) you have a pathological dependency problem, which we’ll address shortly.

构建依赖关系图

Building Dependency Graphs

图 13.2 依赖图

Figure 13.2 A dependency graph

图片

首先,重要的是要考虑我们如何构建依赖关系图。考虑图 13.2中所示的组件集。

First of all, it is important to consider how we can build a graph of dependencies. Consider the set of components shown in Figure 13.2.

投资组合管理应用程序依赖于定价引擎、结算引擎和报告引擎。这些又都依赖于一个框架。定价引擎依赖于(现在苦苦挣扎的)第三方提供的信用违约互换 (CDS) 库。通常,我们将图左侧更远的组件称为“上游”依赖项,将图右侧更远的组件称为“下游”依赖项。因此,定价引擎有两个上游依赖项,即 CDS 定价库和框架,以及一个下游依赖项,即投资组合管理应用程序。

The portfolio management application depends on a pricing engine, a settlement engine, and a reports engine. These in turn all depend on a framework. The pricing engine depends on a credit default swap (CDS) library that is provided by a (now struggling) third party. In general, we refer to a component further to the left of the diagram as an “upstream” dependency, and a component further to the right as a “downstream” dependency. Thus the pricing engine has two upstream dependencies, the CDS pricing library and the framework, and one downstream dependency, the portfolio management application.

每个组件都应该有自己的管道,由该组件源代码的更改或任何上游依赖项的更改触发。下游依赖项将由该组件通过其所有自动化测试触发。在构建这个组件图时,有几种可能的场景需要考虑。

Each component should have its own pipeline, triggered by changes in that component’s source code or by changes to any upstream dependency. Downstream dependencies will be triggered by this component passing all of its automated tests. There are several possible scenarios to consider in terms of building this graph of components.

1.对投资组合管理应用程序进行了更改在这种情况下,只需要重建项目组合管理应用程序。

1. A change is made to the portfolio management application. In this scenario, only the portfolio management application needs to be rebuilt.

2.对报告引擎进行了更改在这种情况下,必须重建报告引擎并通过其所有自动化测试。然后需要重建投资组合管理应用程序,使用新版本的报告引擎和当前版本的定价和结算引擎。

2. A change is made to the reports engine. In this scenario, the reports engine must be rebuilt and pass all its automated tests. Then the portfolio management application needs to be rebuilt, using the new version of the reports engine and the current version of the pricing and settlement engines.

3. CDS定价库发生变化CDS 定价库是第三方二进制依赖项。因此,如果更新了正在使用的 CDS 版本,则需要针对新版本和框架的当前版本重建定价引擎。这反过来应该会触发项目组合管理应用程序的重建。

3. A change is made to the CDS pricing library. The CDS pricing library is a third-party, binary dependency. So if the version of the CDS in use is updated, the pricing engine needs to be rebuilt against the new version and the current version of the framework. This in turn should trigger a rebuild of the portfolio management application.

4.对框架进行了更改如果对框架进行了成功更改,这意味着框架管道通过了测试,则应重建其直接下游依赖项:报告引擎、定价引擎和结算引擎。如果所有这三个依赖项都通过了,那么应该使用所有三个上游依赖项的新版本来重建项目组合管理应用程序。如果三个中间组件构建中的任何一个失败,则不应重建项目组合管理应用程序,并且应将框架组件视为已损坏。该框架应该得到修复,以便其所有三个下游依赖项都通过测试,这反过来应该会导致投资组合管理应用程序通过。

4. A change is made to the framework. If a successful change is made to the framework, meaning that the framework pipeline passes its tests, its immediate downstream dependencies should be rebuilt: the reports engine, the pricing engine, and the settlement engine. If all three of these dependencies pass, then the portfolio management application should be rebuilt using the new versions of all three of its upstream dependencies. If any of the three intermediate component builds fail, the portfolio management application should not be rebuilt and the framework components should be treated as broken. The framework should be fixed so that all three of its downstream dependencies pass their tests, which in turn should lead to the portfolio management application passing.

从这个例子中可以得出一个重要的观察结果。在考虑场景 4 时,项目组合管理应用程序的上游依赖项之间似乎需要某种“和”关系。然而,情况并非如此——如果对报告引擎的源代码进行了更改,则无论是否重建定价引擎或结算引擎,都应该触发投资组合管理应用程序的重建。此外,请考虑以下情况。

There is an important observation to be drawn from this example. When considering scenario 4, it may seem that some kind of “and” relationship is required between the upstream dependencies of the portfolio management application. However, this is not the case—if a change is made to the source of the reports engine, it should trigger a rebuild of the portfolio management application whether or not the pricing engine or settlement engine are rebuilt. Furthermore, consider the following scenario.

5.对框架和定价引擎进行了更改在这种情况下,需要重建整个图。但是有几种可能的结果,每种都有自己的考虑因素。令人高兴的是,所有三个中间组件都通过了新版本的框架和 CDS 定价库。但是,如果结算引擎出现故障怎么办?显然,投资组合管理应用程序不应针对新的(但损坏的)框架版本构建。但是,您可能希望使用新版本的定价引擎构建投资组合管理应用程序,(至关重要的)应该针对新版本的 CDS 定价库和旧的(已知良好的)框架版本构建。当然现在你有麻烦了,因为不存在这样版本的定价库。

5. A change is made to the framework and the pricing engine. In this case, the whole graph needs to be rebuilt. But there are several possible outcomes, each with its own considerations. The happy path is that all three intermediate components pass with the new versions of the framework and the CDS pricing library. But what if the settlement engine fails? Clearly the portfolio management application should not build against the new (but broken) version of the framework. However, you might well want the portfolio management application to build with the new version of the pricing engine, which (crucially) should be built against the new version of the CDS pricing library and the old (known good) version of the framework. Of course now you’re in trouble, because no such version of the pricing library exists.

这些场景最重要的限制是投资组合管理应用程序应该只针对一个版本的框架构建。我们特别不希望最终得到一个版本(比如说)针对一个版本的框架构建的定价引擎,以及针对另一个版本构建的结算引擎。这是经典的“钻石依赖”问题——它是我们在本章前面讨论的运行时“依赖地狱”问题的构建时类比。

The most important constraint on these scenarios is that the portfolio management application should only build against one version of the framework. We particularly don’t want to end up with a version of (say) the pricing engine built against one version of the framework, and the settlement engine built against another version. This is the classic “diamond dependency” problem—which is the build-time analogue of the runtime “dependency hell” problem we discussed earlier in this chapter.

流水线依赖图

Pipelining Dependency Graphs

图 13.3 组件管道

Figure 13.3 Component pipeline

图片

那么我们如何根据上面描述的项目结构构建部署流水线呢?管道的关键要素是团队必须尽快获得有关任何破损的反馈,并且我们应该遵守上述构建依赖关系的规则。我们的方法如图 13.3所示。

So how do we construct a deployment pipeline based on the project structure we describe above? The key elements of the pipeline are that the team must get feedback as rapidly as possible on any breakages, and that we should obey the rules for building dependencies described above. Our approach is shown in Figure 13.3.

有几个重要的功能需要调用。首先,为了提高反馈速度,一旦每个项目管道的提交阶段完成,就会触发依赖项目。您无需等待验收测试通过 - 只需等待创建下游项目所依赖的二进制文件即可。然后将这些存储在您的工件存储库中。当然,验收测试和各种部署阶段将重用这些二进制文件(为了防止混乱,图中未显示)。

There are a couple of important features to call out. First of all, to increase the speed of feedback, dependent projects are triggered once the commit stage of each project’s pipeline is complete. You don’t need to wait for the acceptance tests to pass—only for the binaries to be created which the downstream projects depend on. These are then stored in your artifact repository. Of course the acceptance tests and the various deployment stages will reuse these binaries (this is not shown on the diagram to prevent clutter).

所有触发器都是自动的,除了部署到手动测试和生产环境,通常是手动授权的。这些自动触发器确保任何时候(例如)对框架进行更改时,它都会触发定价引擎、结算引擎和报告引擎的构建。如果所有这三个都使用新版本的框架成功构建,则投资组合管理应用程序将使用所有上游组件的新版本重新构建。

All of the triggers are automatic, with the exception of deployments to the manual testing and production environments, which are generally manually authorized. These automatic triggers ensure that any time a change is made to (for example) the framework, it triggers a build of the pricing engine, settlement engine, and reports engine. If all three of these build successfully with the new version of the framework, the portfolio management application will get rebuilt with the new versions of all the upstream components.

图 13.4 可视化上游依赖关系

Figure 13.4 Visualizing upstream dependencies

图片

团队必须能够追踪进入特定应用程序构建的组件的来源。一个好的 CI 工具不仅可以做到这一点,还会向您展示哪些版本的组件成功集成在一起。例如在图 13.4中,您可以看到 2.0.63 版的投资组合管理应用程序是使用 1.0.217 版的定价引擎、2.0.11 版的结算引擎、1.5.5 版的报告引擎构建的,以及框架版本 1.3.2396。

It is essential that teams can trace the origins of the components that went into a particular build of the application. A good CI tool will not only do this, but will also show you which versions of your components integrated together successfully. For example in Figure 13.4, you can see that version 2.0.63 of the portfolio management application was built with version 1.0.217 of the pricing engine, version 2.0.11 of the settlement engine, version 1.5.5 of the reports engine, and version 1.3.2396 of the framework.

图 13.5 可视化下游依赖关系

Figure 13.5 Visualizing downstream dependencies

图片

图 13.5显示了使用选定版本的框架 (1.3.2394) 构建的所有下游组件。

Figure 13.5 shows all of the downstream components built using the selected version of the framework (1.3.2394).

您的 CI 工具还应确保在整个管道中使用一致版本的组件。它应该防止依赖地狱,并确保影响多个组件的版本控制更改仅通过管道传播一次。

Your CI tool should also ensure that consistent versions of components are used throughout the pipeline. It should prevent dependency hell, and ensure that a change in version control which affects multiple components only propagates through the pipeline once.

我们在本章开头给出的关于增量开发的所有建议也适用于组件。以不破坏依赖关系的增量方式进行更改。添加新功能时,在发生变化的组件中为其提供新的 API 入口点。如果您想弃用旧功能,请使用静态分析作为管道的一部分来检测谁在使用旧 API。如果您的任何更改错误地破坏了任何依赖项,管道应该会迅速告诉您。

All the advice we gave at the beginning of this chapter on incremental development also applies to components. Make changes in an incremental way which doesn’t break your dependencies. When you add new functionality, provide a new API entry point for it in the components that change. If you want to deprecate old functionality, use static analysis as part of your pipeline to detect who is consuming the old APIs. The pipeline should tell you quickly if any of your changes has broken any of your dependencies by mistake.

图 13.6 分支组件

Figure 13.6 Branching components

图片

如果您确实需要对组件进行影响深远的更改,您可以创建它的新版本。图 13.6中,我们假设从事报表引擎工作的团队需要创建一个破坏某些 API 的新版本。为此,他们为 1.0 版本创建了一个分支,并开始在主线上开发 1.1。

If you do need to make a far-reaching change to a component, you can create a new release of it. In Figure 13.6, we assume that the team working on the reports engine needs to create a new version that breaks some APIs. In order to do this, they create a branch for the 1.0 release, and start the development of 1.1 on mainline.

报告引擎团队将继续在主线上添加新功能。同时,报告引擎的下游用户可以继续使用从 1.0 分支创建的二进制文件。如果他们需要修复错误,可以将其签入 1.0 分支并合并到主干中。一旦下游用户准备好使用新版本,他们就可以切换。需要明确的是,此处描述的“按发布分支”模式仍然存在延迟集成的相同缺点,因此在持续集成方面它是第二好的。然而,组件是(或至少应该是)松散耦合的事实意味着以后痛苦的集成风险更可控。所以这是一个非常有用的策略,用于管理对组件的更复杂的更改。

The reporting engine team will continue to add new features on mainline. Meanwhile, downstream users of the reporting engine can continue to use the binaries created from the 1.0 branch. If they need a bugfix, it can be checked into the 1.0 branch and merged into trunk. Once the downstream users are ready to use the new version, they can switch. To be clear, the “branch by release” pattern, as described here, still suffers from the same downside of deferring integration, so it is second best in terms of continuous integration. However, the fact that components are (or at least should be) loosely coupled means that the risks of painful integration later are more controllable. So this is a very useful strategy for managing more complex changes to a component.

我们应该什么时候触发构建?

When Should We Trigger Builds?

上面讨论的所有示例都假设我们在上游依赖项发生任何更改时触发新构建。这是正确的做法,但在许多团队中并不是常态——相反,他们往往只在代码库稳定后才更新依赖项,也许是在集成时,或者是在开发达到其他里程碑时。这种行为强调稳定性,但以花费大量时间进行整合的潜在风险为代价。

All of the examples discussed above assume that we trigger a new build whenever there is any change to upstream dependencies. This is the right thing to do, but it is not the norm in many teams—rather, they tend to only update their dependencies once their codebase is stable, perhaps at integration time, or when development has reached some other milestone. This behavior emphasizes stability, but at the cost of potential risk of spending a great deal of time integrating.

可以看出,在涉及到依赖的开发过程中存在着一种张力。一方面,最好跟上最新版本的上游依赖项,以确保您拥有最新的功能和错误修复。另一方面,集成每个依赖项的最新版本可能会产生成本,因为您可能会花费所有时间来修复这些新版本造成的损坏。当更新风险很低时,大多数团队会在每次发布后妥协并刷新所有依赖项。

It can be seen that there is a tension in the development process where dependencies are involved. On one hand, it is best to keep up with the newest versions of upstream dependencies to make sure that you have the most up-to-date features and bugfixes. On the other hand, there can be a cost to integrating the latest version of every dependency, because you can spend all your time fixing breakages caused by these new versions. Most teams compromise and do a refresh of all their dependencies after every release, when the risks of updating are low.

在决定更新依赖项的频率时,一个关键的考虑因素是您对这些依赖项的新版本的信任程度。如果你有几个组件依赖于你的团队开发的组件,你通常可以非常快速和简单地修复由 API 更改引起的损坏,所以经常集成是最好的。如果组件足够小,最好对整个应用程序进行一次构建——提供最快的反馈。

A key consideration when deciding how often to update dependencies is how much you trust new versions of these dependencies. If you have a few components depending upon a component also developed by your team, you can usually fix breakages caused by API changes very quickly and simply, so integrating often is best. If the components are sufficiently small, it is preferable to have a single build for the whole application—giving the fastest feedback of all.

如果上游依赖项是由您自己组织内的另一个团队开发的,那么最好是在他们自己的管道中独立构建这些组件。然后,您可以决定是在每次更改这些上游组件时都采用最新版本,还是坚持使用特定版本。该决定基于它们更改的频率以及处理它们的团队对问题的响应速度。

If the upstream dependencies are developed by another team within your own organization, it is probably best if these components are built independently in their own pipeline. You can then decide whether or not to take the latest version of these upstream components each time that they are changed, or stick with a particular version. This decision is based on how frequently they change, and how fast the teams working on them respond to problems.

您对组件更改的控制、可见性和影响越小,您就越不信任它,您在接受新版本时应该越保守。不要盲目地更新第三方库,例如,如果没有明显的需要这样做。如果更改不能解决您遇到的问题,请不要进行更新,除非您正在使用的版本不再受支持。

The less control, visibility, and influence you have over changes to a component, the less you trust it, the more conservative you should be about accepting new versions. Don’t blindly take updates to third-party libraries, for example, if there is no obvious need to do so. If the changes don’t fix problems that you have, leave the update alone, unless the version you are using is no longer supported.

在大多数情况下,在集成新版本的依赖项方面,团队最好处于更连续的一端。当然,持续更新所有依赖项在集成(硬件和构建)所花费的资源以及修复错误和集成“未完成”组件版本的问题方面成本更高。

In most cases, it works best for teams to be at the more continuous end in terms of integrating new versions of dependencies. Of course, continually updating all dependencies costs more in terms of resources spent integrating (both hardware and builds) and in terms of fixing bugs and the problems of integrating “unfinished” versions of components.

您需要在获得关于您的应用程序是否要集成的快速反馈和拥有不断向您发送您不关心的破坏的过度活跃构建之间取得平衡。正如 Alex Chaffee [d6tguh] 的一篇论文所述,一个潜在的解决方案是“谨慎乐观”。

You need to strike a balance between getting fast feedback on whether your application is going to integrate and having hyperactive builds that continually spam you with breakages that you don’t care about. One potential solution is “cautious optimism,” as described in a paper by Alex Chaffee [d6tguh].

谨慎乐观

Cautious Optimism

Chaffee 的提议是在依赖图中引入一个新的状态——某个特定的上游依赖是“静态的”、“受保护的”还是“流动的”。静态上游依赖项的更改不会触发新构建。流动的上游依赖项中的更改总是会触发新构建。如果“流动的”上游依赖项中的更改触发构建并且构建失败,则上游依赖项被标记为“受保护”,并且组件被固定到上游依赖项的已知良好版本。“受保护的”上游依赖项的行为类似于静态依赖项——它不会进行新的更改——但它可以提醒开发团队上游依赖项存在需要解决的问题。

Chaffee’s proposal is to introduce a new piece of state into the dependency graph—whether a particular upstream dependency is “static,” “guarded,” or “fluid.” Changes in a static upstream dependency do not trigger a new build. Changes in a fluid upstream dependency always trigger a new build. If a change in a “fluid” upstream dependency triggers a build and the build fails, the upstream dependency is marked “guarded,” and the component is pinned to the knowngood version of the upstream dependency. A “guarded” upstream dependency behaves like a static one—it doesn’t take new changes—but it serves to remind the development team that there is a problem that needs to be resolved with the upstream dependency.

实际上,我们明确了我们不想从哪些依赖项中持续获取更新的偏好。我们还确保应用程序始终是“绿色”的——我们的构建系统将自动排除由于上游依赖项的新版本错误而导致的任何损坏。

Effectively, we are making explicit our preferences in terms of which dependencies we do not want to take updates from continuously. We also ensure that the application is always “green”—our build system will automatically back out any breakage due to a bad new version of an upstream dependency.

图 13.7 谨慎乐观的触发

Figure 13.7 Cautious optimism triggering

图片

让我们来看看我们的依赖图的一部分,如图 13.7所示。我们将为 CDS 定价库和定价引擎之间的依赖关系分配一个流动触发器,为框架和定价引擎之间的依赖关系分配一个静态触发器。

Let’s take part of our dependency graph, as shown in Figure 13.7. We’ll assign a fluid trigger to the dependency between the CDS pricing library and the pricing engine, and a static trigger to the dependency between the framework and the pricing engine.

考虑更新 CDS 定价库和框架的情况。忽略新版本的框架,因为定价引擎和框架之间的触发器是静态的。但是,新版本的 CDS 定价库将触发新构建的定价引擎,因为它的触发器设置为流动的。如果这个新构建的定价引擎失败,触发器将被设置为受保护,并且对 CDS 定价库的进一步更改将不会触发它的新构建。如果构建通过,触发器将保持流畅。

Consider the case where both the CDS pricing library and the framework are updated. The new version of the framework is ignored, because the trigger between the pricing engine and the framework is static. However, the new version of the CDS pricing library will trigger a new build of the pricing engine because its trigger is set to fluid. If this new build of the pricing engine fails, the trigger will get set to guarded, and further changes to the CDS pricing library will not trigger a new build of it. If the build passes, the trigger stays fluid.

然而,谨慎的乐观会导致复杂的行为。让我们将框架和定价引擎之间的触发器设置为流畅的,就像 CDS 定价库一样。如果 CDS 定价库和框架都得到更新,将会有一个新的定价引擎构建。如果定价引擎坏了,您不知道是什么破坏了构建——是新版本的 CDS 定价库还是新版本的框架。您将不得不尝试找出它是什么——与此同时,您的两个触发器都将受到保护。

However, cautious optimism can lead to complex behavior. Let’s set the trigger between the framework and the pricing engine to fluid, just like the CDS pricing library is. In the case where both the CDS pricing library and the framework get updated, there will be one new build of the pricing engine. If the pricing engine breaks, you don’t know what broke the build—the new version of the CDS pricing library or the new version of the framework. You’ll have to try and find which it was—and in the meantime, both of your triggers will become guarded.

Chaffee 提到了一种称为“知情悲观主义”的策略,作为任何依赖跟踪算法实施的起点。在此策略中,每个触发器都设置为“静态”,但是当上游依赖项的新版本可用时,处理下游依赖项的开发人员会收到通知。

Chaffee mentions a strategy called “informed pessimism” as a starting point for any implementation of a dependency tracking algorithm. In this strategy, every trigger is set to “static,” but developers working on downstream dependencies are notified when a new version of their upstream dependency becomes available.

循环依赖

Circular Dependencies

可能最讨厌的依赖问题是循环依赖。当依赖图包含循环时会发生这种情况。最简单的例子是你有一个组件 A,它依赖于另一个组件 B。不幸的是,组件 B 又依赖于组件 A。

Probably the nastiest dependency problem is the circular dependency. This occurs when the dependency graph contains cycles. The simplest example is that you have a component, A, that depends on another component, B. Unfortunately component B in turn depends on component A.

这似乎会导致致命的引导问题。要构建组件 A,我需要构建组件 B,但要构建组件 B,我需要组件 A,依此类推。

This may appear to lead to a fatal bootstrapping problem. To build component A, I need to build component B, but to build component B, I need component A, and so on.

图 13.8 循环依赖构建阶梯

Figure 13.8 Circular dependency build ladder

图片

令人惊讶的是,我们已经看到在其构建系统中具有循环依赖性的成功项目。在这种情况下,您可能会质疑我们对“成功”的定义,但生产中有工作代码,这对我们来说已经足够了。关键是你永远不要以循环依赖开始一个项目——它们往往会在以后悄悄出现。这是可能的,但如果可以避免,则不建议这样做,只要存在可用于构建组件 B 的组件 A 版本,就可以解决此问题。然后您可以使用 B 的新版本来构建新版本A. 这导致了一种“构建阶梯”,如图 13.8所示。

Surprisingly, we have seen successful projects with circular dependencies in their build systems. You may argue with our definition of “successful” in this case, but there was working code in production, which is enough for us. The key point is that you never begin a project with circular dependencies—they tend to creep in later. It is possible, but not recommended if you can avoid it, to survive this problem so long as there is a version of component A that you can use to build component B. You can then use the new version of B to build the new version of A. This results in a kind of “build ladder,” as shown in Figure 13.8.

在运行时,只要组件 A 和 B 同时可用就没有问题。

At run time, there is no problem so long as both components, A and B, are available together.

正如我们所说,我们不推荐使用循环依赖。但是,如果您遇到了一个非常难以避免的情况,那么上述策略就可以奏效。没有构建系统支持这种开箱即用的配置,因此您必须破解您的工具链才能支持它。您还必须注意构建的各个部分如何交互:如果每个组件自动触发其依赖项的构建,那么这两个组件将永远构建,因为的圆度。始终尝试摆脱循环依赖;但是如果你发现自己在一个有它们的代码库中工作,不要绝望——你可以使用构建阶梯作为一个临时的解决方法,直到你可以解决这个问题。

As we have said, we don’t recommend using circular dependencies. But if you ever run into one that is unfeasibly hard to avoid, then the strategy outlined above can work. No build system supports such a configuration out of the box, so you have to hack your toolchain to support it. You will also have to be cautious in how the parts of your build interact: If each component triggers a build of its dependencies automatically, the two components will be building forever because of the circularity. Always try to get rid of circular dependencies; but if you find yourself working in a codebase that has them, don’t despair—you can use the build ladder as a temporary workaround until you can eliminate the problem.

管理二进制文件

Managing Binaries

我们花了相当多的时间来讨论如何在软件中组织构建并拆分成组件。我们已经描述了如何为每个组件创建管道,组件发生变化时触发下游组件管道的策略,以及如何对组件进行分支。但是,我们还没有讨论如何在基于组件的构建中管理二进制文件。这一点很重要,因为在大多数情况下,组件之间应该具有二进制而不是源代码级别的依赖关系。接下来的几页将讨论这个主题。

We have spent a fair amount of time discussing how to organize builds in software split into components. We have described how to create a pipeline for each component, strategies for triggering downstream component pipelines when a component changes, and how to branch components. However, we have not yet discussed how to manage binaries in a component-based build. This is important because in most cases, components should have binary rather than source-level dependencies on one other. The next few pages will deal with this topic.

首先,我们将讨论工件存储库工作背后的一般原则。然后我们将继续描述如何仅使用文件系统来管理二进制文件。在下一节中,我们将描述使用 Maven 来管理依赖项。

First, we’ll discuss the general principles behind the workings of an artifact repository. We will then move on to describe how to manage binaries using only the filesystem. In the next section, we will describe the use of Maven to manage dependencies.

您不必推出自己的工件存储库。市场上有多种产品,包括开源项目 Artifactory 和 Nexus。一些工具,例如 AntHill Pro 和 Go,包括它们自己的工件存储库。

You don’t have to roll your own artifact repository. There are several products on the market, including the open source projects Artifactory and Nexus. Several tools, such as AntHill Pro and Go, include their own artifact repository.

工件存储库应该如何工作

How an Artifact Repository Should Work

工件存储库最重要的属性是它不应包含任何无法复制的内容。您应该能够删除您的工件存储库,而不必担心您将无法重新获得任何有价值的东西。为此,您的版本控制系统需要包含重新创建任何给定二进制文件所需的一切,包括自动构建脚本。

The most important property of an artifact repository is that it should not contain anything that cannot be reproduced. You should be able to delete your artifact repository without worrying that you won’t be able to regain anything valuable. For this to be true, your version control system needs to contain everything required to re-create any given binary, including the automated build scripts.

需要删除工件的原因是它们很大(如果现在还不是,它们将会是)。最终您需要删除它们以释放空间。出于这个原因,我们不建议将工件签入版本控制。如果您可以重新创建它们,则无论如何都不需要。保留已通过所有测试并因此成为发布候选对象的工件当然是值得的。如果您需要回滚到早期版本,或支持使用您软件的旧版本的人,任何已发布的内容也值得保留。

The reason for the need to delete the artifacts is that they are big (and if they aren’t yet, they will be). Ultimately you’ll need to delete them in order to free up space. For this reason, we don’t recommend checking artifacts into version control. If you can re-create them, you don’t need to anyway. It is of course worth keeping around artifacts that have passed all tests and are thereby candidates for release. Anything that has been released is also worth keeping around in the event that you need to roll back to that earlier version, or to support someone using an older version of your software.

无论您保留工件本身多长时间,您都应该始终保留每个工件的哈希值,以便您可以验证任何给定二进制文件的来源。这对于审计目的很重要——例如,如果您不确定在特定环境中部署了哪个应用程序。应该可以获得任何给定二进制文件的 MD5,并使用它来准确找出源代码管理中的哪个修订版本用于创建它。你可以使用你的构建系统来存储这些数据(一些 CI 服务器会为你做这件事),或者你的版本控制系统。无论哪种方式,管理哈希都是配置管理策略的重要组成部分。

However long you keep artifacts themselves, you should always keep a hash of each one so you can verify the source of any given binary. This is important for auditing purposes—for example, if you’re not sure exactly which application is deployed in a particular environment. It should be possible to get the MD5 of any given binary and use this to find out exactly which revision in source control was used to create it. You can either use your build system to store this data (some CI servers will do this for you), or your version control system. Either way, managing hashes is an important part of your configuration management strategy.

最简单的工件存储库是磁盘上的目录结构。通常,此目录结构将位于 RAID 或 SAN 上,因为虽然工件应该是一次性的,但您应该是决定删除它们的人,而不是一些性能不佳的硬件。

The simplest artifact repository is a directory structure on disk. Generally, this directory structure will be on a RAID or a SAN, because while artifacts should be disposable, you should be the one deciding that they can be deleted, not some badly behaved piece of hardware.

此目录结构最重要的约束是它应该使您能够将二进制文件与用于创建它的源代码控制中的版本相关联。通常,您的构建系统会为它运行的每个构建生成一个标签,通常是一个序列号。标签应该很短,这样可以很容易地传达给其他人。它可以在用于创建它的版本控制中包含修订的标识符(假设您没有使用 Git 或 Mercurial 等使用哈希作为标识符的工具)。然后可以将该标签包含在二进制文件的清单中(例如,在 JAR 或 .NET 程序集的情况下)。

The most important constraint on this directory structure is that it should enable you to to associate a binary with the version from source control that was used to create it. Normally, your build system will generate a label, usually a sequence number, for each build that it runs. The label should be short so it can be easily communicated to others. It can include the identifier of the revision in version control used to create it (assuming you’re not using a tool like Git or Mercurial which use hashes for identifiers). This label can then be included in the manifest of the binary (in the case of JARs or .NET assemblies, for example).

为每个管道创建一个目录,并在其中为每个内部版本号创建一个目录。然后可以将构建中的所有工件存储在该目录中。

Create a directory for each pipeline, and within that, a directory for each build number. All the artifacts from the build can then be stored in that directory.

下一个复杂的小步骤是添加一个简单的索引文件,允许您将状态与每个构建相关联,以便您可以记录每个更改在部署管道中的状态。

The next small step in sophistication is to add a simple index file that allows you to associate a status with each build, so that you can record the status of each change as it progresses through the deployment pipeline.

如果您不想为工件存储库使用共享驱动器,您可以添加一个 Web 服务来存储和检索工件。但是,如果您已达到这一点,则应考虑使用市场上众多免费或商业产品中的一种。

If you don’t want to use a shared drive for your artifact repository, you can add a web service to store and retrieve artifacts. However, if you have reached this point, you should consider using one of the many free or commercial products on the market.

您的部署管道应如何与工件存储库交互

How Your Deployment Pipeline Should Interact with the Artifact Repository

您的部署管道实施需要做两件事: 将构建过程生成的工件存储到工件存储库中,然后检索它们供以后使用。

Your deployment pipeline implementation needs to do two things: Store artifacts generated by the build process into the artifact repository, and then retrieve them for later use.

考虑具有以下阶段的管道:编译、单元测试、验收测试、手动验收测试和生产。

Consider a pipeline with the following stages: compile, unit test, acceptance test, manual acceptance test, and production.

• 编译阶段将创建需要放入工件存储库的二进制文件。

• The compile stage will create binaries that need to be put into the artifact repository.

• 单元测试和验收测试阶段将检索这些二进制文件,对它们运行单元测试,并将单元测试生成的报告存储在工件存储库中,以便开发人员可以查看结果。

• The unit test and acceptance test stages will retrieve these binaries, run unit tests against them, and store reports generated by the unit tests in the artifact repository so that developers can see the results.

• 用户验收测试阶段将获取二进制文件并将它们部署到UAT 环境以进行手动测试。

• The user acceptance test stage will take the binaries and deploy them to the UAT environment for manual testing.

• 发布阶段将获取二进制文件并将它们发布给用户或将它们部署到生产环境中。

• The release stage will take the binaries and release them to users or deploy them to production.

随着发布候选通过此管道,每个阶段的成功或失败都会记录在索引中。后续流水线阶段可能取决于此文件中的状态,因此只有通过验收测试的二进制文件才可用于手动测试和后续阶段。

As the release candidate progresses through this pipeline, the success or failure of each stage is recorded in the index. Subsequent pipeline stages can depend on the status in this file, so only binaries that have passed the acceptance tests are made available to manual testing and further stages.

有几个选项可用于将工件移入和移出工件存储库。您可以将它们存储在一个共享文件系统中,该文件系统可从您需要构建或部署到的每个环境中访问。然后您的部署脚本可以引用该文件系统的路径。或者,您可以使用 Nexus 或 Artifactory 等解决方案。

There are a couple of options for getting artifacts in and out of the artifact repository. You can store them in a shared filesystem that is accessible from every environment that you need to build on or deploy to. Your deployment scripts can then reference the path to this filesystem. Alternatively, you could use a solution like Nexus or Artifactory.

使用 Maven 管理依赖关系

Managing Dependencies with Maven

Maven 是一个可扩展的 Java 项目构建管理工具。特别是,它提供了一种用于管理依赖项的复杂机制。即使您不喜欢 Maven 的其余部分,您也可以以独立的方式使用其强大的依赖关系管理功能。或者,您可以使用 Ivy,它只解决依赖管理问题,而没有 Maven 的构建管理工具链的其余部分。如果您不使用 Java,您可以跳过本节,除非您对 Maven 如何解决依赖管理问题感兴趣。

Maven is an extensible build management tool for Java projects. In particular, it provides a sophisticated mechanism for managing dependencies. Even if you don’t like the rest of Maven, you can use its powerful dependency management functionality in a standalone way. Alternatively, you could use Ivy, which tackles only the dependency management problem without the rest of Maven’s build management toolchain. If you’re not using Java, you can probably skip this section, unless you’re interested in how Maven solves the dependency management problem.

如前所述,项目有两种依赖关系:对外部库的依赖关系,我们在第 354 页的“管理库”部分中讨论过,以及应用程序组件之间的依赖关系。Maven 提供了一种抽象,使您可以或多或少地以相同的方式对待它们。所有 Maven 域对象,例如项目、依赖项和插件,都由一组坐标标识:groupIdartifactIdversion,它们一起必须唯一标识一个对象(这些轴有时称为 GAV),以及包装这些通常以以下格式编写,这也是您在 Buildr 中声明它们的方式:groupId:artifactId:packaging:version. 因此,例如,如果您的项目依赖于 Commons Collections 3.2,您可以如下描述该依赖性:commons-collections: commons-collections:jar:3.2

As discussed, projects have two kinds of dependencies: dependencies on external libraries, which we discussed in the “Managing Libraries” section on page 354, and dependencies between the components of your application. Maven provides an abstraction that lets you treat them both in more or less the same way. All Maven domain objects, such as projects, dependencies, and plugins, are identified by a set of coordinates: groupId, artifactId, and version, which together must uniquely identify an object (these axes are sometimes referred to as GAV), as well as packaging. These are often written in the following format, which is also how you declare them in Buildr: groupId:artifactId:packaging:version. So, for example, if your project depends on Commons Collections 3.2, you would describe that dependency as follows: commons-collections: commons-collections:jar:3.2.

Maven 社区维护着一个镜像存储库,其中包含大量常见的开源库及其关联的元数据(包括传递依赖项)。这些存储库几乎包含您在几乎任何项目中可能需要的所有开源库。您可以在 Web 浏览器中浏览此存储库,网址为http://repo1.maven.org/maven2在 Maven 的存储库中声明对库的依赖将导致 Maven 在您构建项目时为您下载它。

The Maven community maintains a mirrored repository that contains a large number of common open source libraries with their associated metadata (including transitive dependencies). These repositories contain almost every open source library you might need in almost any project. You can browse this repository in a web browser at http://repo1.maven.org/maven2. Declaring a dependency on a library in Maven’s repository will result in Maven downloading it for you when you build your project.

您使用名为 pom.xml 的文件在 Maven 中声明一个项目,如下所示:

You declare a project in Maven using a file called pom.xml as follows:

图片

这将在构建项目时将 JUnit 3.8.1 版和 Commons Collections 3.2 版获取到位于 ~/.m2/repository/<groupId>/<artifactId>/<version>/ 的本地 Maven 工件存储库。本地 Maven 工件存储库有两个用途:它是项目依赖项的缓存,也是 Maven 存储项目创建的工件的地方(稍后会详细介绍)。请注意,您还可以指定依赖项的范围:test表示依赖项仅在测试编译和组装期间可用。其他有效范围包括编译不需要的依赖项的运行时,为编译时需要但将在运行时提供的库提供,以及编译(默认值)用于编译时和运行时所需的依赖项。

This will fetch version 3.8.1 of JUnit and version 3.2 of Commons Collections to your local Maven artifact repository at ~/.m2/repository/<groupId>/ <artifactId>/<version>/ when the project is built. The local Maven artifact repository serves two purposes: It is a cache for dependencies of your projects, and it is also where Maven stores the artifacts created by your projects (more on that shortly). Notice that you can also specify the scope of the dependency: test means the dependency will only be available during test compilation and assembly. Other valid scopes include runtime for dependencies that are not required for compilation, provided for libraries that are required at compile time but will be provided at run time, and compile (the default) for dependencies that are required at compile time and run time.

您还可以指定版本范围,例如[1.0,2.0)将为您提供 1.x 系列中的任何版本。括号表示排他量词,方括号表示包含量词。您可以省略左侧或右侧,因此[2.0,)表示高于 2.0 的任何版本。然而,即使您想在选择版本时给 Maven 一定的自由度,指定一个上限以避免您的项目选择可能破坏您的应用程序的新的主要修订版也是一个好主意。

You can also specify version ranges, such as [1.0,2.0) which will give you any version in the 1.x series. Brackets indicate exclusive quantifiers, and square brackets indicate inclusive quantifiers. You can leave out either the left side or the right side—so [2.0,) means any version higher than 2.0. However, even if you want to give Maven some latitude when choosing versions, it’s a good idea to specify an upper bound to avoid your project picking up new major revisions which may break your application.

该项目还将创建自己的工件:一个 JAR 文件,该文件将存储在本地存储库中 pom.xml 中指定的坐标处。在上面的示例中,运行mvn install将导致在本地 Maven 工件存储库中创建以下目录:~/.m2/repository/com/continuousdelivery/parent/1.0.0/。由于我们选择了 JAR 包装类型,Maven 会将您的代码打包到一个名为 parent-1.0.0.jar 的 JAR 中,并将其安装到该目录中。我们在本地运行的任何其他项目现在都可以通过将其坐标指定为依赖项来访问此 JAR。Maven 还将在同一目录中安装项目 pom 的修改版本,其中包括有关其依赖项的信息,以便 Maven 能够正确处理传递依赖项。

This project will also create an artifact of its own: a JAR file which will be stored in your local repository at the coordinates specified in your pom. In the example above, running mvn install will result in the the following directory being created in your local Maven artifact repository: ~/.m2/repository/com/continuousdelivery/parent/1.0.0/. Since we have selected packaging type JAR, Maven will package your code into a JAR called parent-1.0.0.jar and install it into this directory. Any other project that we run locally can now access this JAR by specifying its coordinates as a dependency. Maven will also install a modified version of your project’s pom into the same directory, which includes information on its dependencies so that Maven is able to process transitive dependencies correctly.

通常您不希望每次运行mvn install时都覆盖您的工件。为了做到这一点,Maven 提供了快照构建的概念。只需将-SNAPSHOT附加到版本(因此在上面的示例中,它将是1.0.0-SNAPSHOT。然后,当您运行mvn install时,Maven 将在版本号目录的位置创建一个格式为version的目录-yyyymmdd-hhmmss-n . 然后,使用您的快照的项目可以仅指定1.0.0-SNAPSHOT,而不是完整的时间戳,并将获得本地存储库具有的最新版本。6个

Often you won’t want to overwrite your artifacts every time you run mvn install. In order to do this, Maven provides the concept of snapshot builds. Simply append -SNAPSHOT to the version (so in the example above, it would be 1.0.0-SNAPSHOT. Then, when you run mvn install, in place of the directory with the version number, Maven will create a directory in the format version-yyyymmdd-hhmmss-n. Projects that consume your snapshots can then specify only 1.0.0-SNAPSHOT, not the full timestamp, and will be given the latest version that the local repository has.6

但是,您应该谨慎使用快照,因为它会使再现构建变得更加困难。一个更好的想法是让您的 CI 服务器生成每个依赖项的规范版本,使用构建标签作为工件版本号的一部分,并将这些存储在您组织的中央工件存储库中。然后,您可以在 pom 文件中使用 Maven 的版本限定符来指定要使用的可接受版本范围。如果你真的需要在你的本地机器上做一些探索性的工作,你总是可以编辑你的 pom 定义来临时启用快照。

However, you should use snapshots with care because it can make reproducing builds harder. A better idea is to have your CI server produce canonical versions of each dependency, using the build label as part of the artifact’s version number, and store these in your organization’s central artifact repoistory. You can then use Maven’s version quantifiers in your pom file to specify a range of acceptable versions to use. If you really need to do some exploratory work on your local machine, you can always edit your pom definition to temporarily enable snapshots.

在本节中,我们只触及了 Maven 的皮毛。特别是,我们还没有讨论管理您自己的 Maven 存储库,如果您想管理整个组织的依赖关系或多模块项目(这是 Maven 创建组件化构建的方式),这一点很重要。虽然这些都是重要的主题,但它们超出了我们在本章中可以合理涵盖的范围。如果您对更高级的 Maven-fu 感兴趣,我们建议您查阅优秀的Maven:由 Sonatype 编写并由 O'Reilly 出版的权威指南。同时,我们确实想介绍一些您可以在 Maven 中进行的基本依赖重构。

We have only scratched the surface of Maven in this section. In particular, we haven’t discussed managing your own Maven repositories, which is important if you want to manage dependencies across your organization, or multimodule projects which are Maven’s way of creating a componentized build. While these are important topics, they go beyond what we can reasonably cover in this chapter. If you’re interested in more advanced Maven-fu, we recommend you consult the excellent Maven: The Definitive Guide written by Sonatype and published by O’Reilly. Meanwhile, we do want to cover some of the basic dependency refactorings you can do in Maven.

Maven 依赖重构

Maven Dependency Refactorings

假设您有一组由多个项目使用的依赖项。如果您只想定义要使用一次的工件版本,您可以通过定义一个包含要使用的每个工件版本的父项目来实现。只需采用上面提供的 POM 定义,并将 <dependencyManagement> 包裹< dependencies>块周围。然后你可以像这样定义一个子项目:

Say you have a set of dependencies that are used by mutiple projects. If you only want to define the versions of the artifacts to use once, you can do so by defining a parent project which includes the versions of each artifact to use. Just take the POM definition provided above, and wrap <dependencyManagement> around the <dependencies> block. Then you can define a child project like this:

图片

这将使用父项目中定义的这些依赖项的版本——请注意,junitcommons-collections引用没有指定版本号。

This will use the versions of these dependencies defined in the parent project—notice that the junit and commons-collections references have no version number specified.

您还可以重构 Maven 构建以删除常见依赖项的重复。您可以让 Maven 项目创建一个 pom,然后由其他项目引用,而不是创建 JAR 作为其最终产品。在第一个代码清单(带有artifactId parent)中,您可以将<packaging>的值更改为pom而不是jar然后,您可以在任何要使用相同依赖项的项目中声明对此 pom 的依赖项:

You can also refactor your Maven build to remove duplication of common dependencies. Instead of creating a JAR as its end product, you can have a Maven project create a pom which is then referenced by other projects. In the first code listing (with artifactId parent), you would change the value of <packaging> to pom instead of jar. You can then declare a dependency on this pom in any projects that you want to use the same dependencies:

图片

Maven 的一个非常有用的功能是它可以分析项目的依赖项并告诉您未声明的依赖项和未使用的已声明依赖项。只需使用mvn dependency:analyze即可运行此报告。这里有更多关于使用 Maven 管理依赖关系的信息:[cxy9dm]。

One really useful feature of Maven is that it can analyze your project’s dependencies and tell you about both undeclared dependencies and unused declared dependencies. Simply use mvn dependency:analyze to run this report. There’s more on managing dependencies with Maven here: [cxy9dm].

概括

Summary

在本章中,我们讨论了确保您的团队能够尽可能高效地开发,同时使您的应用程序始终处于可发布状态的技术。与往常一样,原则是确保团队能够快速获得反馈,了解他们的更改对应用程序生产就绪性的影响。实现这一目标的一个策略是确保将每个更改分解为小的、增量的步骤,并检查到主线中。另一种方法是将您的应用程序分解为组件。

In this chapter, we have discussed techniques to ensure that your team can develop as efficiently as possible, while keeping your application always in a releasable state. As usual, the principle is to ensure that teams get fast feedback on the effect of their changes on the production-readiness of the application. One strategy for meeting this goal is to ensure every change is broken down into small, incremental steps which are checked into mainline. Another is to break your application down into components.

将应用程序划分为松散耦合、封装良好、协作的组件的集合不仅是好的设计。在大型系统上工作时,它允许更有效的协作和更快的反馈。在你的应用程序变得足够大之前,没有必要单独构建你的组件——最简单的事情是在第一阶段使用一个管道来一次性构建你的整个应用程序。如果您专注于高效的提交构建和快速单元测试,并实施用于验收测试的构建网格,您的项目可以发展到您认为可能的更大程度。一个最多 20 人的团队全职工作几年应该不需要创建多个构建管道,当然他们仍然应该将他们的应用程序分成组件。

Dividing an application into a collection of loosely coupled, well-encapsulated, collaborating components is not only good design. It allows for more efficient collaboration and faster feedback when working on large systems. Until your application gets sufficiently large, there is no need to build your components individually—the simplest thing is to have a single pipeline that builds your whole application at once as the first stage. If you concentrate on efficient commit builds and fast unit testing, and implement build grids for acceptance testing, your project can grow to a much larger degree that you might think possible. A team of up to 20 people working full-time for a couple of years should not need to create multiple build pipelines, although of course they should still separate their application into components.

但是,一旦超过这些限制,组件的使用、基于依赖的构建管道和有效的工件管理就是高效交付和快速反馈的关键。本章中描述的方法的美妙之处在于它建立在基于组件的设计已经有益的实践之上。这种方法避免了使用复杂的分支策略,这通常会导致在集成您的应用程序时出现严重问题。然而,它确实取决于拥有一个设计良好的应用程序,该应用程序适合组件化构建。不幸的是,我们已经看到太多无法以这种方式轻松组件化的大型应用程序。很难将这样的应用程序引导到可以轻松修改和集成的状态。所以,

Once you exceed these limits, though, the use of components, dependencybased build pipelines, and effective artifact management are the key to efficient delivery and fast feedback. The beauty of the approach described in this chapter is that it builds on the already beneficial practice of component-based design. This approach avoids the use of complex branching strategies, which usually leads to serious problems in integrating your application. However, it does depend on having a well-designed application that is amenable to a componentized build. Unfortunately, we have seen too many large applications that cannot be easily componentized in this way. It is very hard to coax such an application into a state where it can be easily modified and integrated. So, make sure that you are using your technology’s toolchain effectively to write code that can be built as a set of independent components once it gets large enough.

第 14 章高级版本控制

Chapter 14. Advanced Version Control

介绍

Introduction

版本控制系统,也称为源代码控制和修订控制系统,旨在使组织能够维护对其应用程序所做的每一次更改的完整历史记录,包括源代码、文档、数据库定义、构建脚本、测试等。然而,它们还有另一个重要目的:它们使团队能够在应用程序的不同部分上协同工作,同时维护一个记录系统——应用程序的最终代码库。

Version control systems, also known as source control and revision control systems, are designed to allow organizations to maintain a complete history of every change made to their applications, including source code, documentation, database definitions, build scripts, tests, and so forth. However, they also serve another important purpose: They enable teams to work together on separate parts of an application while maintaining a system of record—the definitive codebase of the application.

一旦您的团队发展到超出少数开发人员的规模,就很难让很多人全职工作在同一个版本控制存储库上。人们会错误地破坏彼此的功能,并且通常会踩到彼此的脚趾。因此,本章的目的是研究团队如何有效地使用版本控制。

Once your team grows beyond a handful of developers, it becomes hard to have many people working full-time on the same version control repository. People break each other’s functionality by mistake, and generally tread on each others’ toes. The aim of this chapter is thus to examine how teams can work productively with version control.

我们将从一点历史开始,然后直接深入到版本控制中最具争议的话题:分支和合并。然后我们继续讨论一些避免传统工具的一些问题的现代范例:基于流的版本控制和分布式版本控制。最后,我们将展示一组使用分支的模式——或者在某些情况下,避免它们。

We’ll start off with a little history, and then dive straight into the most controversial topic in version control: branching and merging. We then go on to discuss some modern paradigms that avoid some of the problems of traditional tools: stream-based revision control and distributed revision control. Finally, we’ll present a set of patterns for working with branches—or, in some cases, for avoiding them.

在本章中,我们将花费大量时间讨论分支和合并。因此,让我们花点时间考虑一下它如何适合我们花了很多时间讨论的部署管道。部署管道是一种以受控方式将代码从签入转移到生产的范例。但是,它只是您在大型软件系统中必须使用的三个自由度之一。本章和上一章讨论了其他两个维度:分支和依赖关系。

We’ll be spending a lot of time discussing branching and merging in this chapter. So let’s take a moment to think of how it fits into the deployment pipeline we’ve spent so much time discussing. The deployment pipeline is a paradigm for moving code from check-in to production in a controlled way. However, it is only one of the three degrees of freedom that you have to work with in large software systems. This and the previous chapter address the other two dimensions: branches and dependencies.

分支代码有三个很好的理由。首先,可以创建一个分支来发布应用程序的新版本。这使开发人员可以继续开发新功能,而不会影响稳定的公开发布。当发现错误时,首先在相关的公共发布分支中修复它们,然后将更改应用到主线。发布分支永远不会合并回主线。其次,当您需要推出新功能或重构时;spike 分支被丢弃并且永远不会合并。最后,当您需要对上一章中描述的任何方法都无法对应用程序进行重大更改时,创建一个短期分支是可以接受的——如果您的代码库结构良好,这种情况极为罕见。该分支的唯一目的是使代码库达到可以增量或通过抽象分支进行进一步更改的状态。

There are three good reasons to branch your code. First, a branch can be created for releasing a new version of your application. This allows developers to continue working on new features without affecting the stable public release. When bugs are found, they are first fixed in the relevant public release branch, and then the changes are applied to the mainline. Release branches are never merged back to mainline. Second, when you need to spike out a new feature or a refactoring; the spike branch gets thrown away and is never merged. Finally, it is acceptable to create a short-lived branch when you need to make a large change to the application that is not possible with any of the methods described in the last chapter—an extremely rare scenario if your codebase is well structured. The sole aim of this branch is to get the codebase to a state where further change can be made either incrementally or through branch by abstraction.

版本控制简史

A Brief History of Revision Control

所有版本控制系统的鼻祖是 SCCS,它于 1972 年由贝尔实验室的 Marc J. Rochkind 编写。大多数古老的开源版本控制系统都是从它演变而来的,所有这些系统仍在使用:RCS、CVS 和 Subversion。1当然,市场上有许多商业工具,每种工具都有自己的方法来帮助软件开发人员管理协作。其中最流行的是 Perforce、StarTeam、ClearCase、AccuRev 和 Microsoft Team Foundation System。

The grand-daddy of all version control systems was SCCS, written in 1972 by Marc J. Rochkind at Bell Labs. From it evolved most of the venerable open source version control systems, all still in use: RCS, CVS, and Subversion.1 Of course there are many commercial tools on the market, each with its own approach to helping software developers manage collaboration. The most popular of these are Perforce, StarTeam, ClearCase, AccuRev, and Microsoft Team Foundation System.

版本控制系统的发展并没有放缓,目前有一个有趣的趋势是向分布式版本控制系统发展。DVCS 的创建是为了支持大型开源团队的工作模式,例如 Linux 内核开发团队。我们将在后面的部分中介绍分布式版本控制系统。2个

The evolution of revision control systems has not slowed, and currently there is an interesting movement toward distributed version control systems. DVCSs were created to support the working patterns of large open source teams, such as the Linux kernel development team. We’ll look at distributed version control systems in a later section.2

由于 SCCS 和 RCS 现在很少使用,我们不会在这里讨论它们;专门的 VCS 爱好者可以在网上找到大量信息。

Since SCCS and RCS are so rarely used today, we won’t discuss them here; dedicated VCS junkies can find plenty of information online.

简历

CVS

CVS 代表并发版本系统。在这种情况下,“并发”意味着多个开发人员可以同时在同一个存储库上工作。CVS 是在 RCS 之上实现的开源包装器,3提供了额外的功能,例如客户端-服务器架构和更强大的分支和标记功能。最初由 Dick Grune 于 1984–1985 年编写,并于 1986 年作为一组 shell 脚本公开提供,1988 年由 Brian Berliner 移植到 C。多年来,CVS 一直是世界上最著名和最受欢迎的版本控制系统,主要是因为它是唯一免费的 VCS。

CVS stands for Concurrent Versions System. “Concurrent” in this context means that multiple developers can work at the same time on the same repository. CVS is an open source wrapper implemented on top of RCS,3 which provides extra features such as a client-server architecture and more powerful branching and tagging facilities. Originally written in 1984–1985 by Dick Grune and made publicly available in 1986 as a set of shell scripts, it was ported to C in 1988 by Brian Berliner. For many years, CVS was the best known and most popular version control system in the world, mainly because it was the only free VCS.

CVS 为版本控制和软件开发过程带来了许多创新。可能其中最重要的是默认CVS 的行为不是锁定文件(因此是“并发”)——事实上,这是 CVS 开发的主要动机。

CVS brought a number of innovations both to versioning and to the software development process. Probably the most important of these is that the default behavior of CVS is not to lock files (hence “concurrent”)—in fact, this was the principal motivation for CVS’ development.

尽管进行了创新,但 CVS 仍存在许多问题,其中一些问题是由于它从 RCS 继承了每个文件的更改跟踪系统。

Despite its innovations, CVS has many problems, some of which are due to its inheriting a per-file change tracking system from RCS.

• CVS 中的分支涉及将每个文件复制到存储库的新副本中。如果您的存储库很大,这可能会花费很长时间并占用大量磁盘空间。

• Branching in CVS involves copying every file into a new copy of the repository. This can take a long time and use a lot of disk space if you have a big repository.

• 由于分支是副本,从一个分支合并到另一个分支可能会给您带来很多幻影冲突,并且不会自动将新添加的文件从一个分支合并到另一个分支。有变通办法,但它们很耗时、容易出错,而且完全令人不快。

• Since branches are copies, merging from one branch into another can give you lots of phantom conflicts, and does not automatically merge newly added files from one branch into another. There are workarounds, but they are time-consuming, error-prone, and altogether thoroughly unpleasant.

• CVS 中的标记涉及接触存储库中的每个文件——这是大型存储库中的另一个耗时过程。

• Tagging in CVS involves touching every file in the repository—another time-consuming process in large repositories.

• 签入CVS 不是原子的。这意味着如果您的签入过程被中断,您的存储库将处于中间状态。同样,如果两个人同时尝试签入,则两个来源的更改可能会交错。这使得很难看出谁更改了什么,或者回滚一组更改。

• Check-ins to CVS are not atomic. This means that if your check-in process gets interrupted, your repository will be left in an intermediate state. Similarly, if two people try to check in at the same time, the changes from both sources may be interleaved. This makes it hard to see who changed what, or to roll back one set of changes.

• 重命名文件不是一流的操作:您必须删除旧文件并添加新文件,在此过程中会丢失修订历史记录。

• Renaming a file is not a first-class operation: You have to delete the old file and add a new one, losing the revision history in the process.

• 建立和维护存储库是一项艰巨的工作。

• Setting up and maintaining a repository is hard work.

• 二进制文件只是 CVS 中的 blob。它不尝试管理对二进制文件的更改,因此磁盘使用效率低下。

• Binary files are just blobs in CVS. It makes no attempt to manage changes to binary files, so disk usage is inefficient.

颠覆

Subversion

Subversion (SVN) 旨在成为“更好的 CVS”。它解决了 CVS 的许多问题,并且通常可以在任何情况下用作 CVS 的高级替代品。它旨在为 CVS 用户所熟悉,并保留基本相同的命令结构。这种熟悉帮助 Subversion 在应用软件开发中迅速取代 CVS。

Subversion (SVN) was designed to be “a better CVS.” It fixes many of CVS’ problems, and in general can be used as a superior replacement to CVS in any situation. It was designed to be familiar to users of CVS, and retains essentially the same command structure. This familiarity has helped Subversion rapidly replace CVS in application software development.

SVN 的许多优良品质源于放弃了 SCCS、RCS 及其衍生产品的通用格式。在 SCCS 和 RCS 中,文件是版本控制的单位:每个签入的文件在版本库中都有一个文件。在 SVN 中,版本控制的单位是修订,它包括对一组目录中文件的一组更改.4您可以将每个修订视为包含当时存储库中所有文件的快照。除了描述文件的变化,增量可以包括复制和删除文件的指令。在 SVN 中,每次提交都以原子方式应用所有更改并创建新修订。

Many of the good qualities of SVN derive from abandoning the format common to SCCS, RCS, and their derivatives. In SCCS and RCS, files are the unit of versioning: There is a file in the repository for every file checked in. In SVN, the unit of versioning is the revision, which comprises a set of changes to the files in a set of directories.4 You can think of each revision as containing a snapshot of all the files in the repository at that time. In addition to describing changes to files, deltas can include instructions for copying and deleting files. In SVN, every commit applies all changes atomically and creates a new revision.


图片

Subversion 提供了一种称为“外部”的工具,它允许您将远程存储库挂载到存储库中的指定目录。如果您的代码依赖于其他一些代码库,则此工具很有用。Git 提供了一个类似的工具,称为“子模块”。这提供了一种简单而廉价的方法来管理系统中组件之间的依赖关系,同时仍然为每个组件维护一个存储库。您还可以使用此方法将源代码和任何大型二进制文件(编译器、工具链的其他部分、外部依赖项)分离到单独的存储库中,同时仍使用户能够看到它们之间的链接。

Subversion provides a facility known as “externals” which allows you to mount a remote repository to a specified directory in your repository. This facility is useful if your code depends on some other codebase. Git offers a similar facility called “submodules.” This provides a simple and cheap way to manage dependencies between components in your system, while still maintaining one repository per component. You can also use this method to separate your source code and any large binaries (compilers, other parts of your toolchain, external dependencies) into separate repositories, while still enabling users to see the links between them.


Subversion 的存储库模型最重要的特征之一是修订号全局应用于存储库而不是单个文件。您不能再谈论单个文件从修订版 1 移动到修订版 2。相反,您可能想知道当存储库从修订版 1 更改为修订版 2 时特定文件发生了什么。Subversion 处理目录、文件属性和元数据它处理文件的方式相同,这意味着对这些对象的更改可以像对文件的更改一样进行版本控制。

One of the most important characteristics of Subversion’s repository model is that revision numbers apply globally to the repository rather than to individual files. You can no longer talk about an individual file moving from revision 1 to revision 2. Instead, you would want to know what happened to a particular file when the repository changed from revision 1 to revision 2. Subversion treats directories, file attributes, and metadata the same way it treats files, which means that changes to these objects can be versioned in the same way as changes to files.

Subversion 中的分支和标记也得到了很大改进。Subversion 不是更新每个单独的文件,而是利用其写时复制存储库的速度和简单性。按照惯例,每个 Subversion 存储库中都有三个子目录:trunk、tags 和 branches。要创建分支,只需在branches目录下创建一个带有分支名称的目录,然后将要分支的修订版中的trunk内容复制到刚刚创建的新分支目录中。

Branching and tagging in Subversion are also much improved. Instead of updating each individual file, Subversion leverages the speed and simplicity of its copy-on-write repository. By convention, there are three subdirectories in every Subversion repository: trunk, tags, and branches. To create a branch, you simply create a directory with the branch name under the branches directory, and copy the contents of trunk at the revision you want to branch from to the new branch directory you just created.

因此,您刚刚创建的分支只是指向主干所指向的同一组对象的指针——直到分支和主干开始分叉。因此,Subversion 中的分支几乎是恒定时间的操作。标签的处理方式完全相同,只是它们存储在名为tags的目录下。Subversion 不区分标签和分支,所以区别只是一种约定。如果需要,您可以将标记的修订视为 Subversion 中的一个分支。

The branch you just created is thus simply a pointer to the same set of objects that the trunk points to—until the branch and trunk begin to diverge. As a result, branching in Subversion is an almost constant-time operation. Tags are handled in exactly the same way, except they are stored under a directory called tags. Subversion does not distinguish between tags and branches, so the difference is simply a convention. If you want, you can treat a tagged revision as a branch in Subversion.

Subversion 还改进了 CVS,保留了每个文件版本的本地副本,就像您上次从中央存储库中检出它时一样。这意味着许多操作(例如,检查您在工作副本中所做的更改)可以在本地执行,这使得它们比在 CVS 中快得多。它们甚至可以在中央存储库不可用时完成,这使得在与网络断开连接时继续工作成为可能。

Subversion also improves on CVS by keeping a local copy of the version of every file as it existed when you last checked it out from the central repository. This means that many operations (for example, checking what you have changed in your working copy) can be performed locally, making them much faster than in CVS. They can even be done when the central repository is not available, which makes it possible to continue working while disconnected from the network.

然而,客户端-服务器模型仍然使一些事情变得困难:

However, the client-server model still makes some things difficult:

• 您只能在联机时提交更改。这听起来很明显,但分布式版本控制系统的主要优点之一在于签入是一个独立于将更改发送到另一个存储库的操作。

• You can only commit changes while online. This might sound obvious, but one of the main advantages of distributed version control systems lies in the fact that checking in is an operation separate from sending your changes to another repository.

• SVN 用于跟踪本地客户端更改的数据存储在存储库中每个文件夹的.svn 目录中。可以将本地系统上的不同目录更新为不同的修订版,甚至更新为不同的标签或分支。虽然这可能是可取的,但在某些情况下可能会导致混乱甚至错误。

• The data that SVN uses to track changes on local clients is stored in .svn directories in each folder in the repository. It is possible to update different directories on your local system to different revisions, and even to different tags or branches. While this may be desirable, in some cases it can lead to confusion and even errors.

• 虽然服务器操作是原子的,但客户端操作不是。如果客户端更新中断,工作副本可能会处于不一致状态。通常,这很容易修复,但在某些情况下,有必要删除整个子树并再次检查它们。

• While server operations are atomic, client-side operations are not. If a clientside update is interrupted, the working copy can end up in an inconsistent state. Generally, this is fairly easy to fix, but in some cases it is necessary to delete whole subtrees and check them out again.

• 修订号在给定的存储库中是唯一的,但在不同的存储库中不是全局唯一的。这意味着,例如,如果出于某种原因将存储库分成较小的存储库,则新存储库中的修订号将与旧存储库没有任何关系。虽然这听起来像是一件小事,但这意味着 SVN 存储库无法支持分布式版本控制系统的某些功能。

• Revision numbers are unique in a given repository, but not globally unique across different repositories. This means, for example, that if a repository is broken into smaller repositories for some reason, the revision numbers in the new repositories will not bear any relationship to the old ones. While this may sound like a small thing, it means that SVN repositories cannot support some features of distributed version control systems.

Subversion 无疑代表了 CVS 的巨大进步。较新版本的 Subversion 具有合并跟踪等功能,这使得它在功能丰富性(如果不是在性能和​​可伸缩性方面)接近 Perforce 等商业工具。然而,与 Git 和 Mercurial 等新一代分布式版本控制系统相比,它开始显示出其最初的灵感所强加的局限性,即“更好的 CVS”。正如 Linus Torvalds 所说,“没有办法正确地执行 CVS”[9yLX5I]。

Subversion certainly represents a great advance over CVS. More recent versions of Subversion have features such as merge tracking which make it approach commercial tools like Perforce in feature-richness, if not in performance and scalability. However, when compared to the new crop of distributed version control systems such as Git and Mercurial, it begins to show the limitations imposed by its original inspiration to be “a better CVS.” As Linus Torvalds notably said, “There is no way to do CVS right” [9yLX5I].

不过,如果您对集中式版本控制系统的局限性感到满意,那么 Subversion 可能就足够了。

Nevertheless, if you are comfortable with the limitations of a centralized version control system, Subversion may be good enough for you.

商业版本控制系统

Commercial Version Control Systems

软件工具的世界瞬息万变,因此本节很可能会过时。查看http://continuousdelivery.com以获取最新信息。在撰写本文时,我们能够全心全意推荐的唯一商业 VCS 是:

The world of software tools moves fast, so this section is likely to go out of date. Check http://continuousdelivery.com for the most up-to-date information. At the time of writing, the only commercial VCSs that we are able to wholeheartedly recommend are:

性能卓越的性能、可扩展性和出色的工具支持。Perforce 用于一些真正庞大的软件开发组织。

Perforce. Superior performance, scalability, and excellent tool support. Perforce is used in some truly huge software development organizations.

AccuRev提供类似 ClearCase 的能力来进行基于流的开发,而不会产生与 ClearCase 相关的严重管理开销和低性能。

AccuRev. Offers ClearCase-like ability to do stream-based development without the crippling administrative overhead and poor performance associated with ClearCase.

比特守护者第一个真正的分布式版本控制系统,并且仍然是唯一的商业版本。

BitKeeper. The first truly distributed version control system, and still the only commercial one.

如果您使用 Visual Studio,Microsoft 的 Team Foundation Server (TFS) 可能是您的默认选择——它的紧密集成可能是它唯一的区别。否则,没有充分的理由使用它的源代码控制产品,因为它本质上是 Perforce 的劣质仿制品。Subversion 轻而易举地战胜了 TFS。我们强烈建议您尽可能避免使用 ClearCase、StarTeam 和 PVCS。任何仍在使用 Visual SourceSafe 的人都应该立即迁移到一个在很多情况下都不会破坏其数据库的工具(这是版本控制系统中的一个大禁忌)5 [c5uyOn]。对于简单的迁移路径,我们建议使用 SourceGear 的优秀产品 Vault(TFS 也提供了一个简单的迁移路径,但我们不推荐它)。

Microsoft’s Team Foundation Server (TFS) may be your default choice if you use Visual Studio—its tight integration is perhaps its only distinction. Otherwise, there is no good reason to use its source control offering, since it is essentially an inferior knock-off of Perforce. Subversion wins over TFS hands down. We strongly suggest that you avoid ClearCase, StarTeam, and PVCS wherever possible. Anybody still using Visual SourceSafe should immediately migrate to a tool which doesn’t corrupt its database (a big no-no in a version control system) in quite so many situations5 [c5uyOn]. For an easy migration path, we’d suggest SourceGear’s excellent product Vault (TFS also offers an easy migration path, but we cannot recommend it).

关闭悲观锁定

Switch Off Pessimistic Locking

如果你的版本控制系统支持乐观锁定,在你的本地工作副本中编辑文件不会阻止其他人在他们的工作副本中编辑它,你应该使用它。悲观锁定,您必须获得对文件的独占锁定才能对其进行编辑,这似乎是防止合并冲突的好方法。但是,在实践中,它会降低开发过程的效率,尤其是在较大的团队中。

If your version control system supports optimistic locking, in which editing a file in your local working copy doesn’t prevent others from editing it in theirs, you should use it. Pessimistic locking, in which you must obtain an exclusive lock on a file in order to edit it, may seem like a good way to prevent merge conflicts. However, in practice it reduces the efficiency of the development process, especially in larger teams.

采用悲观锁方法的版本控制系统根据所有权进行处理。悲观锁定策略确保任何时候只有一个人可以处理任何给定的对象。如果 Tom 试图在 Amrita 检出版本控制时获取组件 A 的锁,他将被打包。如果他在未先获取锁的情况下尝试提交更改,则操作将失败。

Version control systems that take the pessimistic lock approach deal in terms of ownership. The pessimistic locking strategy ensures that only one person can work on any given object at any time. If Tom attempts to acquire a lock on component A while Amrita has it checked out of revision control, he will be sent packing. If he attempts to commit a change without first acquiring the lock, the operation will fail.

乐观锁系统以完全不同的方式工作。他们不是控制访问,而是基于这样的假设:大多数时候,人们不会在同一件事上工作,因此允许系统的所有用户自由访问他们控制下的所有对象。这些系统跟踪对其控制下的对象的更改,并且在提交更改的时候使用算法来合并更改。通常合并是完全自动的,但如果版本控制系统检测到一个不能自动合并的变更,它会突出显示变更并向提交变更的人寻求帮助。

Optimistic lock systems work in a completely different manner. Instead of controlling access, they work on the assumption that most of the time, people won’t be working on the same thing, and so allow free access for all users of the system to all objects under their control. These systems track changes to the objects under their control, and when the time comes to commit changes, they use algorithms to merge the changes. Usually the merging is completely automatic, but if the revision control system detects a change that it cannot merge automatically, it will highlight the change and ask for help from the person committing the change.

乐观锁系统的工作方式通常根据它们管理的内容的性质而有所不同。对于二进制文件,他们倾向于忽略增量,只接受最后提交的更改。然而,他们的力量在于他们处理源代码的方式。对于这样的对象,乐观锁系统通常假设文件中的一行是一个合理的更改单元。因此,如果 Ben 处理组件 A 并更改第 5 行,同时 Tom 处理组件 A 并更改第 6 行,则在两者都提交后,版本控制系统将保留 Ben 的第 5 行和 Tom 的第 6 行。如果他们都决定更改第 7 行, 并且 Tom 首先签入,当他签入时,版本控制系统将提示 Ben 解决由此产生的合并冲突。他将被要求要么保留 Tom 的更改,保留他自己的更改,要么手动编辑它们以保留两者的重要部分。

The way optimistic lock systems work usually varies depending on the nature of the content they are managing. For binary files, they tend to ignore deltas, and just take the last change submitted. However, their power lies in the way they deal with source code. For such objects, optimistic lock systems often assume that a single line within a file is a sensible unit of change. So if Ben works on component A and changes line 5 while, simultaneously, Tom is working on component A and changes line 6, after both commit the revision control system will keep Ben’s line 5 and Tom’s line 6. If both decide to change line 7, and Tom checks in first, Ben will be prompted by the version control system to resolve the resulting merge conflict when he makes his check-in. He will be asked to either keep Tom’s change, keep his own, or manually edit them together to keep the important bits of both.

对于习惯于悲观锁版本控制系统的人来说,乐观锁系统有时看起来,嗯,乐观得无可救药。“他们怎么可能工作?” 实际上,它们的效果出奇地好,在许多方面都比悲观锁定好得多。

For people accustomed to the pessimistic lock revision control systems, optimistic lock systems sometimes look, well, hopelessly optimistic. “How can they possibly work?” Actually, they work surprisingly well, in many respects significantly better than pessimistic locking.

我们听说悲观锁定系统的用户表示担心乐观锁定系统的用户会把所有的时间都花在解决合并冲突上,或者自动合并会导致代码无法执行甚至无法编译。这些担心根本没有在实践中实现。合并冲突确实会发生——在大型团队中它们发生得相当频繁——但通常几乎所有冲突都在几秒钟而不是几分钟内得到解决。如果您忽略我们之前的建议并且没有足够频繁地提交更改,它们只会比这花费更长的时间。

We have heard users of pessimistic locking systems express fears that users of optimistic locking systems will spend all of their time resolving merge conflicts, or that the automated merging will result in code that doesn’t execute or even compile. These fears are simply not realized in practice. Merge conflicts do happen—on large teams they happen fairly frequently—but usually nearly all of them are fixed in a matter of seconds rather than minutes. They only take longer than that if you ignore our earlier recommendation and don’t commit changes frequently enough.


图片

悲观锁定唯一有意义的情况是二进制文件,例如图像和文档。在这种情况下,不可能有意义地合并结果,因此悲观锁定是一种合理的方法。Subversion 允许您按需锁定文件,并且还可以将属性svn:needs-lock应用于此类文件以强制执行悲观锁定。

The only time when pessimistic locking makes sense is for binary files, such as images and documents. In this case, it’s impossible to merge the results meaningfully, so pessimistic locking is a reasonable approach. Subversion allows you to lock files on demand, and also to apply a property, svn:needs-lock, to such files to enforce pessimistic locking.


悲观系统通常会迫使开发团队按组件分配行为,以避免因等待访问同一代码而导致的长时间延迟。创造力的流动——开发过程中一个自然而重要的部分——经常被检查开发人员没有意识到需要的文件的需要打断。它们还使得几乎不可能在不给许多其他用户带来不便的情况下进行影响大量文件的更改。在离开主线工作的大型团队中,团队几乎不可能在启用悲观锁定的情况下进行重构。

Pessimistic systems often force development teams to allocate behavior by component to avoid lengthy delays caused by waiting for access to the same code. The flow of creativity—a natural and essential part of the development process—is frequently interrupted by the need to check out a file that the developer hadn’t realized would be needed. They also make it almost impossible to make changes that affect a large number of files without inconveniencing many other users. On large teams working off mainline, it is virtually impossible for teams to refactor with pessimistic locking switched on.

乐观锁定对开发过程施加的约束更少。版本控制系统不会对您强加任何策略。总的来说,它在使用中给人的感觉明显减少了,而且重量更轻,不会失去任何灵活性或可靠性,并且可扩展性大大提高,特别是对于大型分布式团队而言。如果您的版本控制系统有这个选项,请选择乐观锁定。如果没有,请考虑迁移到可以的版本控制系统。

Optimistic locking imposes fewer constraints on the development process. The version control system doesn’t impose any strategy on you. Overall, it feels significantly less intrusive and lighter-weight in use, without losing any flexibility or reliability and with a great increase in scalability, particularly for large, distributed teams. If your revision control system has the option, pick optimistic locking. If it doesn’t, consider migrating to a revision control system that does.

分支与合并

Branching and Merging

在代码库中创建分支或流的能力是每个版本控制系统的一流功能。此操作在版本控制系统中创建所选基线的副本。然后可以用与原件相同的方式(但独立于)操作此复制品,从而使两者有所不同。分支的主要目的是促进并行开发:同时处理两个或多个工作流而不影响另一个的能力。例如,发布分支很常见,允许在主线上进行持续开发并在发布分支中修复错误。团队可能选择分支他们的代码还有其他几个原因。6个

The ability to create branches, or streams, in a codebase is a first-class feature of every version control system. This operation creates a replica of the chosen baseline within the version control system. This replica can then be manipulated in the same way as (but independently from) the original, allowing the two to diverge. The main purpose of branches is to facilitate parallel development: the ability to work on two or more work streams at the same time without one affecting the other. For example, it is common to branch on release, allowing for ongoing development on mainline and bugfixing in the release branch. There are several other reasons why teams may choose to branch their code.6

物理:系统物理配置的分支——为文件、组件和子系统创建分支。

Physical: branching of the system’s physical configuration—branches are created for files, components, and subsystems.

功能:系统功能配置的分支——为功能、逻辑更改、错误修复和增强以及其他重要的可交付功能单元(例如,补丁、发布和产品)创建分支。

Functional: branching of the system’s functional configuration—branches are created for features, logical changes, both bugfixes and enhancements, and other significant units of deliverable functionality (e.g., patches, releases, and products).

环境:系统操作环境的分支——为构建和运行时平台(编译器、窗口系统、库、硬件、操作系统等)和/或整个平台的各个方面创建分支。

Environmental: branching of the system’s operating environment—branches are created for various aspects of the build and runtime platforms (compilers, windowing systems, libraries, hardware, operating systems, etc.) and/or for the entire platform.

组织:团队工作的分支——为活动/任务、子项目、角色和组创建分支。

Organizational: branching of the team’s work efforts—branches are created for activities/tasks, subprojects, roles, and groups.

程序性:团队工作行为的分支——创建分支以支持各种策略、流程和状态。

Procedural: branching of the team’s work behaviors—branches are created to support various policies, processes, and states.

这些类别并不相互排斥,但它们提供了对人们分支原因的洞察力。当然,您可以同时创建多个维度的分支;如果分支永远不必相互交互,这很好。然而,通常情况并非如此——通常我们必须在一个称为合并的过程中从一个分支中获取一组更改并将其复制到另一个分支。

These categories aren’t mutually exclusive, but they provide an insight into the reasons why people branch. Of course, you could create branches across several dimensions at the same time; this is fine if the branches never have to interact with each other. However, this is normally not the case—usually we have to take a set of changes from one branch and copy it to another branch in a process known as merging.

在我们开始合并之前,值得考虑一下分支产生的问题。在大多数分支情况下,整个代码库将在每个分支中单独发展——包括测试用例、配置、数据库脚本等。首先,它强调了将所有内容绝对保留在版本控制中的必要性。在开始对代码库进行分支之前,请确保您已准备就绪——确保您拥有在版本控制中构建软件所需的一切。

Before we get on to merging, it is worth thinking about the problems that branching creates. In most cases where you branch, your entire codebase is going to evolve separately in each branch—including test cases, configuration, database scripts, and so forth. First of all, it highlights the imperative of keeping absolutely everything in version control. Before you start branching your codebase, make sure that you’re ready—ensure you have absolutely everything you need to build your software in version control.

分支和流似乎是解决影响大型团队软件开发过程的许多问题的好方法。然而,合并分支的要求意味着在分支之前仔细考虑并确保您有一个合理的流程来支持它是很重要的。特别是,您需要为每个分支机构定义一个策略,描述其在交付过程中的角色,并规定允许哪些人以及在什么情况下签入。例如,一个小团队可能有一个所有开发人员都可以检查的主线和一个只有测试团队才能批准更改的发布分支。然后测试团队将负责将错误修复合并到发布分支中。

Branching and streaming may seem like a great way to solve many problems affecting the process of software development on large teams. However, the requirement to merge branches means it’s important to think carefully before branching and to make sure you have a sensible process to support it. In particular, you need to define a policy for each branch describing its role in the delivery process and prescribing who is allowed to check into it and under what circumstances. For example, a small team might have a mainline which all developers can check into and a release branch that only the testing team is able to approve changes to. The testing team would then be responsible for merging bugfixes into the release branch.

在一个规模更大、监管更严格的组织中,每个组件或产品可能都有开发人员签入的主线,以及只有操作人员有权进行更改的集成分支、发布分支和维护分支。将更改引入这些分支可能需要创建更改请求并让代码通过一组测试(手动或自动)。将定义一个提升过程,例如,更改必须从主线到集成分支,然后才能提升到发布分支。Berczuk (2003),第 117–127 页中更详细地讨论了代码行策略。

In a larger and more heavily regulated organization, each component or product might have a mainline that developers check into, and integration branches, release branches, and maintenance branches that only operations personnel are authorized to make changes to. Getting changes into these branches might require creating change requests and having the code pass a set of tests (manual or automated). There will be a promotion process defined, so that, for example, changes must go from mainline to the integration branch before they can be promoted to the release branch. Code line policies are discussed in more detail in Berczuk (2003), pp. 117–127.

合并

Merging

分支就像量子力学的多世界解释所假设的无限宇宙。每个人都是完全独立的,并且存在于对其他人的幸福无知中。然而,在现实生活中,除非您为发布或峰值创建分支,否则您将达到需要将在一个分支中所做的更改应用到另一个分支的地步。这样做可能非常耗时,尽管市场上几乎每个 VCS 都有一些功能可以使它更容易,并且分布式 VCS 可以相对简单地合并分支而不会发生冲突。

Branches are like the infinitude of universes postulated by the many-worlds interpretation of quantum mechanics. Each one is completely independent and exists in blissful ignorance of the others. However, in real life, unless you are branching for releases or for spikes, you will reach a point where you need to take the changes you have made in one branch and apply them to another. Doing this can be very time-consuming, although pretty much every VCS on the market has some functionality to make it easier, and distributed VCSs make merging branches with no conflicts relatively straightforward.

当在要合并的两个分支中进行了两个不同且相互冲突的更改时,就会出现真正的问题。如果更改确实相互重叠,您的版本控制系统将检测到它们并警告您。但是,您的冲突可能只是版本控制系统遗漏并自动“合并”的意图差异。当合并之间经过很长时间时,合并冲突通常是功能实现冲突的症状,导致重写大块代码以协调两个分支中发生的更改。在不知道代码作者意图的情况下合并此类更改是不可能的 - 因此必须进行对话,可能是在最初编写要合并的代码几周后。

The real problem arises when two different and conflicting changes have been made in the two branches that you want to merge. Where changes literally overlap each other, your revision control system will detect them and warn you. However, your conflicts may just be differences in intent which are missed by the revision control system and “merged” automatically. When a long time passes between merges, merge conflicts are often symptoms of conflicting implementations of functionality, leading to rewrites of large chunks of the code in order to harmonize the changes that have occurred in the two branches. It is impossible to merge such changes without knowing what the authors of the code intended—so conversations have to happen, perhaps weeks after the code being merged was originally written.

未被版本控制系统捕获的语义冲突可能是最有害的。例如,如果 Kate 在她的一个更改中执行重命名类的重构,而 Dave 在他的一个更改中引入对该类的新引用,那么他们的合并将正常工作。在静态类型语言中,当有人试图编译代码时,就会发现这个问题。在动态语言中,它直到运行时才会被发现。通过合并可以引入更多微妙的语义冲突,如果没有全面的自动化测试,您甚至可能在缺陷发生之前无法发现它们。

Semantic conflicts that are not caught by your version control system can be some of the most pernicious. For example, if Kate perform a refactoring that renames a class in one of her changes, and Dave introduces a new reference to the class in one of his changes, their merge will work just fine. In a statically typed language, this problem will be found when somebody tries to compile the code. In a dynamic language, it won’t be found until run time. Much more subtle semantic conflicts can be introduced through merges, and without a comprehensive body of automated tests, you may not even catch them until a defect occurs.

在合并分支之前你离开的时间越长,你在这些分支上工作的人越多,你的合并就会越不愉快。有一些方法可以最大程度地减少这种痛苦:

The longer you leave things before merging the branches, and the more people you have working on them, the more unpleasant your merge is going to be. There are ways of minimizing this pain:

• 您可以创建更多分支以减少对给定分支所做更改的数量。例如,您可以在每次开始处理某个功能时创建一个分支;这是“早期分支”的一个例子。然而,这意味着需要做更多的工作来跟踪所有分支,而您只是在延迟必须进行更多合并的痛苦。

• You could create more branches to reduce the number of changes made to a given branch. For example, you could create a branch every time you start working on a feature; this is an example of “early branching.” However, this means more work to keep track of all the branches, and you’re just delaying the pain of having to do more merges.

• 您可以在创建分支方面精打细算,也许每个版本都创建一个分支。这是“延迟分支”的示例。为了尽量减少合并的痛苦,你可以经常合并,这意味着合并会不那么令人不快。但是,您必须记住定期执行此操作——例如,每天。

• You could be parsimonious about creating branches, perhaps creating a branch per release. This is an example of “deferred branching.” To minimize the pain of merging, you could merge often, which means the merges will be less unpleasant. However, you have to remember to do it at regular intervals—every day, for example.

事实上,有许多可能的分支模式,每种模式都有自己的策略、优点和缺点。我们将在本章后面探讨一些可能的分支样式。

In fact, there are many possible branching patterns, each with their own policies, advantages, and disadvantages. We’ll explore some possible branching styles later on in this chapter.

分支、流和持续集成

Branches, Streams, and Continuous Integration

敏锐的读者会注意到使用分支和持续集成之间存在紧张关系。如果团队的不同成员在不同的分支或流上工作,那么根据定义,他们并不是在持续集成。也许是使持续集成成为可能的最重要的实践是每个人每天至少检查一次主线。所以如果你每天一次将你的分支合并到(不仅仅是从)主线,你就可以了。如果你不这样做,你就没有进行持续集成。事实上,有一种观点认为,从精益意义上讲,分支机构的任何工作都是浪费——没有被拉入成品的库存。

Keen readers will notice that there is a tension between using branches and continuous integration. If different members of the team are working on separate branches or streams, then by definition they’re not continuously integrating. Perhaps the most important practice that makes continuous integration possible is that everybody checks in to mainline at least once a day. So if you merge your branch to (not just from) mainline once a day, you’re OK. If you’re not doing that, you’re not doing continuous integration. Indeed, there is a school of thought that any work on a branch is, in the lean sense, waste—inventory that is not being pulled into the finished product.

图 14.1 分支控制不佳的典型例子

Figure 14.1 A typical example of poorly controlled branching

图片

看到持续集成基本上被忽略并且人们混杂分支的情况并不少见,导致涉及许多分支的发布过程。我们的同事 Paul Hammant从他从事的一个项目中提供了图 14.1中的示例。

It’s not uncommon to see continuous integration basically ignored and people branching promiscuously, leading to a release process that involves many branches. Our colleague Paul Hammant provided the example in Figure 14.1 from a project he worked on.

在此示例中,为作为开发应用程序的工作计划的一部分出现的各种项目创建了分支。合并回到主干(或这里提到的“集成分支”)时相当不规律,当它们确实发生时,它们往往会破坏它。结果,主干在发布之前的项目“集成阶段”之前会长时间保持中断状态。

In this example, branches are created for various projects that occur as part of the program of work to develop the application. Merges happen back to trunk (or the “integration branch,” as it’s referred to here) fairly irregularly, and when they do happen, they tend to break it. As a result, trunk stays broken for long periods of time until the “integration phase” of the project prior to release.

不幸的是,这个策略的问题是相当典型的,分支往往会在很长一段时间内处于无法部署的状态。此外,分支之间通常存在软依赖关系。在给出的示例中,每个分支都需要从集成分支中获取错误修复,并且每个分支都从性能调整分支中获取性能修复。应用程序的自定义版本的分支是一项正在进行的工作,在很长一段时间内都无法部署。

The problem with this, unfortunately fairly typical, strategy is that the branches tend to stay in an undeployable state for large amounts of time. Furthermore, it is usually the case that branches have soft dependencies on one other. In the example given, every branch needs to take bugfixes from the integration branch, and every branch takes performance fixes from the performance tuning branch. The branch for the custom version of the application is a work in progress that doesn’t become deployable for an extended period of time.

跟踪分支、确定合并什么以及何时合并,然后实际执行这些合并会消耗大量资源,即使使用 Perforce 或 Subversion 等工具提供的合并点跟踪功能也是如此。即使在这完成之后,团队仍然必须让代码库进入可部署状态——这正是持续集成应该解决的问题。

Keeping track of branches, working out what to merge and when, and then actually performing these merges consumes significant resources, even with the merge point tracking facilities provided by tools like Perforce or Subversion. Even after this is done, the team still has to get the codebase into a deployable state—the exact problem that continuous integration is supposed to solve.

图 14.2 发布分支策略

Figure 14.2 Release branching strategy

图片

一种更易于管理的分支策略——我们的强烈建议,并且可以说是行业标准——是仅在发布时创建长期存在的分支,如图 14.2所示。

A more manageable branching strategy—our strong recommendation, and arguably the industry standard—is to create long-lived branches only on release, as shown in Figure 14.2.

在这个模型中,新工作总是提交给主干。仅当必须对发布分支进行修复时才执行合并,然后将其合并到主线中。关键错误修复也可能从主线合并到发布分支中。这种模型更好,因为代码随时可以发布,因此发布更容易。分支更少,因此合并和跟踪分支所需的工作更少。

In this model, new work is always committed to the trunk. Merging is only performed when a fix has to be made to the release branch, from which it is then merged into mainline. Critical bugfixes might also be merged from mainline into release branches. This model is better because the code is always ready to be released, and the releases are therefore easier. There are fewer branches, so much less work has to be done merging and keeping track of the branches.

您可能担心不分支会妨碍您在不影响其他人的情况下创建新功能的能力。如何在不创建新分支来隔离工作的情况下执行大型重组?我们在前一章第 346 页的“保持您的应用程序可发布”部分详细讨论了这个问题。

You may be concerned that not branching will hinder your ability to create new features without affecting other people. How can you perform a large restructuring without creating a new branch to isolate the work? We addressed this at length in the previous chapter, in the “Keeping Your Application Releasable” section on page 346.

与创建分支机构并投入精力重新架构和开发新功能相比,增量方法当然需要更多的纪律和细心——实际上也需要更多的创造力。但它显着降低了您的更改破坏应用程序的风险,并将为您和您的团队节省大量时间来合并、修复损坏以及让您的应用程序进入可部署状态。此类活动往往很难计划、管理和跟踪,这使得它最终比在主线上进行更严格的开发实践成本更高。

The incremental approach certainly requires more discipline and care—and indeed more creativity—than creating a branch and diving gung-ho into rearchitecting and developing new functionality. But it significantly reduces the risk of your changes breaking the application, and will save you and your team a great deal of time merging, fixing breakages, and getting your application into a deployable state. Such activity tends to be very hard to plan, manage, and track, making it ultimately much more costly than the more disciplined practice of developing on mainline.

如果您在中型或大型团队中工作,此时您可能会怀疑地摇头。在没有人员分支的情况下如何处理大型项目?如果每天有 200 人签到,那就是 200 次合并和 200 次构建。没有人会完成任何工作——他们会把所有的时间都花在合并上!

If you work in medium or large teams, you may be shaking your head skeptically at this point. How is it possible to work on a large project without having people branch? If 200 people are checking in every day, that’s 200 merges and 200 builds. Nobody is going to get any work done—they will spend all of their time merging instead!

在实践中,即使每个人都在一个巨大的代码库中工作,事情也可以在大型团队中进行。200 次合并没问题,前提是每个人都在代码的不同区域工作并且每次更改都很小。在一个如此大的项目中,如果几个开发人员经常接触相同的代码位,则表明代码库结构不佳,封装不充分且耦合度高。

In practice, even if everybody is working in one huge codebase, things can work with large teams. Two hundred merges are fine, provided everyone is working in a different area of the code and each change is small. On a project that large, if several developers routinely touch the same bits of code, that indicates that the codebase is poorly structured, with insufficient encapsulation and high coupling.

如果合并一直保留到发布结束,情况会更糟。到那时,几乎可以保证每个分支都会与其他分支发生合并冲突。我们已经看到项目的集成阶段从数周开始尝试解决合并冲突并使应用程序进入甚至可以运行的状态。只有这样,项目的测试阶段才能开始。

Things are much, much worse if merges are left until the end of the release. By that point, it is practically guaranteed that every branch will have merge conflicts with every other. We have seen projects where the integration phase began with weeks of trying to resolve merge conflicts and get the application into a state where it could even be run. Only then could the testing phase of the project even get off the ground.

中型和大型团队的正确解决方案是将您的应用程序拆分为组件,并确保组件之间存在松耦合。这些是精心设计的系统的属性。采用这种渐进式合并方法(其中应用程序始终保持在主线上运行)的结果是它施加了温和、微妙的压力,使您的软件设计更好。将组件集成到一个工作的应用程序中本身就是一个复杂而有趣的问题,我们在前一章中探讨过。但是,它是解决开发大型应用程序问题的一种无限优越的方法。

The correct solution for medium and large teams is to split up your application into components and ensure there is loose coupling between components. These are the properties of well-designed systems. A consequence of taking this incremental approach to merging, in which the application is always kept working on mainline, is the gentle, subtle pressure it applies to make the design of your software better. Integrating the components into a working application is then a complex and interesting problem in its own right, which we explored in the previous chapter. However, it is an infinitely superior way to solve the problem of developing large applications.

值得再次强调的是:您永远不应该使用长期存在的、不经常合并的分支作为管理大型项目复杂性的首选方法。这样做会在您尝试部署或发布您的应用程序时留下麻烦。您的整合过程将是一项风险极高且不可预测的工作,会花费您大量的时间和金钱。任何版本控制系统供应商告诉您,您所要做的就是使用他们的合并工具来解决您的问题,这只是为了节省成本。

It’s worth saying again: You should never use long-lived, infrequently merged branches as the preferred means of managing the complexity of a large project. Doing so stores up trouble for when you come to try and deploy or release your application. Your integration process will be an extremely high-risk exercise that will be unpredictable, costing you considerable time and money. Any version control system vendor telling you that all you have to do is use their merge tools to solve your problem is simply being economical with the truth.

分布式版本控制系统

Distributed Version Control Systems

在过去几年中,分布式版本控制系统 (DVCS) 变得越来越流行。存在几个强大的开源 DVCS,例如 Git [9Xc3HA] 和 Mercurial。在本节中,我们将研究 DVCS 的特殊之处以及如何使用它们。

In the last few years, distributed version control systems (DVCSs) have become increasingly popular. Several powerful open source DVCSs exist, such as Git [9Xc3HA] and Mercurial. In this section, we’ll examine what is special about DVCSs and how to use them.

什么是分布式版本控制系统?

What Is a Distributed Version Control System?

DVCS 背后的基本设计原则是每个用户都在他们的计算机上保留一个独立的、一流的存储库。不需要有特权的“主”存储库,尽管大多数团队按照惯例指定一个(否则不可能进行持续集成)。从这个设计原则出发,许多有趣的特性随之而来。

The fundamental design principle behind a DVCS is that each user keeps a self-contained, first-class repository on their computer. There is no need for a privileged “master” repository, although most teams designate one by convention (otherwise it is impossible to do continuous integration). From this design principle, many interesting characteristics follow.

• 您可以在几秒钟内开始使用 DVCS——只需安装它,然后将您的更改提交到本地存储库。

• You can start using a DVCS in a few seconds—just install it, and commit your changes into a local repository.

• 您可以从其他用户那里单独提取更新,而无需他们将更改检查到中央存储库中。

• You can pull updates individually from other users without them having to check their changes into a central repository.

• 您可以将更新推送给选定的一组用户,而无需强制每个人都接受更新。

• You can push updates to a selected group of users without everyone being forced to take them.

• 补丁可以有效地在用户网络中传播,从而更容易批准或拒绝单个补丁(这种做法称为cherry-picking

• Patches can effectively propagate through networks of users, making it much easier to approve or reject individual patches (a practice known as cherry-picking.

• 您可以在脱机工作时将更改签入源代码管理。

• You can check your changes into source control while you are working offline.

• 您可以定期将不完整的功能提交到本地存储库以检查点,而不会影响其他用户。

• You can commit incomplete functionality regularly to your local repository to check point without affecting other users.

• 在将更改发送给其他人之前,您可以轻松地在本地修改、重新排序或批处理您的提交(这称为变)。

• You can easily modify, reorder, or batch up your commits locally before you send changes to anybody else (this is known as rebasing).

• 无需在中央存储库中创建分支即可轻松地在本地存储库中尝试想法。

• It’s easy to try out ideas in a local repository without the need to create a branch in a central repository.

• 由于能够在本地批量签入,中央存储库不会经常受到攻击,从而使 DCVS 更具可扩展性。

• Due to the ability to batch check-ins locally, the central repository doesn’t get hit so often, making DCVSs more scalable.

• 本地代理存储库易于建立和同步,易于提供高可用性。

• Local proxy repositories are easily established and synchronized, making it easy to provide high availability.

• 由于完整存储库有很多副本,因此DCVS 具有更高的容错能力,但仍应备份主存储库。

• Since there are many copies of the full repository, DCVSs are more fault-tolerant, although master repositories should still be backed up.

图 14.3 DCVS 存储库中的开发线

Figure 14.3 Lines of development in a DCVS repository

图片

如果您认为使用 DVCS 听起来像是每个人都有自己的 SCCS 或 RCS,那么您是对的。分布式版本控制系统与上一节中方法的不同之处在于它们处理多个用户或并发的方式。它不是使用带有版本控制系统的中央服务器来确保几个人可以同时在代码库的同一个分支上工作,而是采用相反的方法:每个本地存储库本身就是一个有效的分支,并且没有“主线”(图 14.3)。

If you think that using a DVCS sounds rather like everyone having their own SCCS or RCS, you are right. Where distributed version control systems differ from the approaches in the previous section is in the way they handle multiple users, or concurrency. Instead of using a central server with a version control system on it to ensure that several people can work on the same branch of the codebase at the same time, it takes the opposite approach: Every local repository is effectively a branch in its own right, and there is no “mainline” (Figure 14.3).

DVCS 设计中的大部分工作都花在了使用户能够轻松地彼此共享他们的更改上。正如 Ubuntu 母公司 Canonical 的创始人 Mark Shuttleworth 所指出的那样,“分布式版本控制的美妙之处在于自发团队的形成,因为对错误或功能有共同兴趣的人们开始着手解决它,并在两者之间反弹工作他们通过发布分支和彼此合并。当分支和合并的成本降低时,这些团队更容易形成,将这一点发挥到极致表明,对开发人员的合并体验进行投资是非常值得的。”

Much of the work that goes into the design of a DVCS is spent on making it easy for users to share their changes with each other. As Mark Shuttleworth, founder of Ubuntu’s parent company Canonical, notes, “The beauty of distributed version control comes in the form of spontaneous team formation, as people with a common interest in a bug or feature start to work on it, bouncing that work between them by publishing branches and merging from one another. These teams form more easily when the cost of branching and merging is lowered, and taking this to the extreme suggests that it’s very worthwhile investing in the merge experience for developers.”

随着 GitHub、BitBucket 和 Google Code 的出现,这种现象尤为明显。使用这些站点,开发人员可以轻松地复制现有项目的存储库,进行更改,然后让可能对其感兴趣的其他用户轻松获得他们的更改。维护者原始项目的开发者可以看到更改,如果他们愿意,可以将它们拉回到他们项目的主存储库中。

This phenomenon is especially visible with the advent of GitHub, BitBucket, and Google Code. Using these sites, it’s easy for developers to make a copy of an existing project’s repository, make a change, and then make their changes easily available to other users who might be interested in them. The maintainers of the original project can see the changes and pull them back into their project’s master repository if they like.

这代表了合作模式的转变。人们不必将补丁提交给项目所有者以提交回项目存储库,人们现在可以发布自己的版本供其他人试验。这导致项目的更快发展、更多的实验以及功能和错误修复的更快交付。如果有人做了一些聪明的事情,其他人可以并且将会使用它。这意味着提交访问不再是人们创建新功能或修复错误的瓶颈。

This represents a paradigm shift in collaboration. Instead of having to submit their patches to the project owner for committing back to the project’s repository, people can now publish their own version for others to experiment with. This leads to much faster evolution of projects, much more experimentation, and faster delivery of features and bugfixes. If somebody does something clever, other people can and will use it. That means that commit access is no longer a bottleneck to people creating new functionality or fixing bugs.

分布式版本控制系统简史

A Brief History of Distributed Version Control Systems

多年来,Linux 内核的开发都没有使用源代码控制。Linus Torvalds 在他自己的机器上开发,并以 tarball 的形式提供源代码,这些 tarball 被迅速复制到世界范围内的大量系统中。所有更改都作为补丁发送给他,他可以轻松应用和退出。因此,他不需要源代码控制——既不需要备份他的源代码,也不需要允许多个用户同时在存储库上工作。

For a number of years, the Linux kernel was developed without the use of source control. Linus Torvalds developed on his own machine and made the source available as tarballs which were rapidly copied to a vast number of systems worldwide. All changes were sent to him as patches, which he could easily apply and back out. As a result, he didn’t need source control—neither for backing up his source code nor to allow multiple users to work on the repository at the same time.

然而,在 1999 年 12 月,Linux PowerPC 项目开始使用 BitKeeper,这是一种专有的分布式版本控制系统,于 1998 年面市。Linus 开始考虑采用 BitKeeper 来维护内核。在接下来的几年中,一些部分的维护者内核开始使用它。最终,在 2002 年 2 月,Linus 采用了 BitKeeper,将其描述为“完成这项工作的最佳工具”,尽管它不是开源产品。

However, in December 1999, the Linux PowerPC project began using BitKeeper, a proprietary distributed version control system which became available in 1998. Linus began to consider adopting BitKeeper for maintaining the kernel. Over the course of the following years, some of the maintainers of sections of the kernel began to use it. Eventually, in February 2002, Linus adopted BitKeeper, describing it as “the best tool for the job,” despite not being an open source product.

BitKeeper 是第一个广泛使用的分布式版本控制系统,它建立在 SCCS 之上。事实上,BitKeeper 存储库仅由一组 SCCS 文件组成。为了与分布式版本控制系统的理念保持一致,每个用户的 SCCS 存储库本身就是一流的存储库。BitKeeper 是 SCCS 之上的一个层,它允许用户将增量或针对特定修订的更改视为一流的域对象。

BitKeeper was the first widely used distributed version control system, and it was built on top of SCCS. In fact, a BitKeeper repository consists simply of a set of SCCS files. In keeping with the philosophy of distributed version control systems, each user’s SCCS repository is a first-class repository in its own right. BitKeeper is a layer on top of SCCS which allows users to treat deltas, or changes against a particular revision, as first-class domain objects.

继 BitKeeper 之后,许多开源 DVCS 项目开始了。其中第一个是 Arch,由 Tom Lord 于 2001 年创建。Arch 不再维护,并已被 Bazaar 取代。今天有许多相互竞争的开源 DVCS。其中最流行和功能最丰富的是 Git(由 Linus Torvalds 创建以维护 Linux 内核并被许多其他项目使用)、Mercurial(被 Mozilla Foundation、OpenSolaris 和 OpenJDK 使用)和 Bazaar(被 Ubuntu 使用) . 其他正在积极开发的开源 DVCS 包括 Darcs 和 Monotone。

Following BitKeeper, a number of open source DVCS projects started. The first of these was Arch, begun by Tom Lord in 2001. Arch is no longer maintained, and has been superseded by Bazaar. Today there are many competing open source DVCSs. The most popular and feature-rich of these are Git (created by Linus Torvalds to maintain the Linux kernel and used by many other projects), Mercurial (used by the Mozilla Foundation, OpenSolaris, and OpenJDK), and Bazaar (used by Ubuntu). Other open source DVCSs under active development include Darcs and Monotone.

企业环境中的分布式版本控制系统

Distributed Version Control Systems in Corporate Environments

在撰写本文时,商业组织在采用 DVCS 方面进展缓慢。除了普遍的保守主义之外,还有三个明显反对在公司中使用 DVCS 的人。

At the time of writing, commercial organizations had been slow to adopt DVCSs. Apart from general conservatism, there are three obvious objections to the use of DVCSs in companies.

• 与只在用户计算机上存储单一版本存储库的集中式版本控制系统不同,任何制作 DVCS 本地存储库副本的人都拥有其完整的历史记录。

• Unlike centralized version control systems, which only store a single version of the repository on the user’s computer, anyone who makes a copy of the local repository of a DVCS has its entire history.

• 审计和工作流是 DVCS 领域中更难以把握的概念。集中式版本控制系统要求用户将所有更改检查到中央存储库中。DVCS 允许用户相互发送更改,甚至可以在他们的本地存储库中更改历史记录,而无需在中央系统中跟踪这些更改。

• Auditing and workflow are more slippery concepts in the realm of DVCS. Centralized version control systems require users to check all their changes into a central repository. DVCSs allow users to send changes to each other, and even to change history in their local repository, without these changes being tracked in the central system.

• Git 实际上允许您更改历史记录。在受监管制度约束的企业环境中,这很可能是一条红线,他们必须定期备份他们的存储库,以便记录发生的一切。

• Git actually does allow you to change history. This may well be a red line in corporate environments subject to regulatory regimes, who will have to back up their repository regularly in order to keep a record of everything that has happened.

实际上,在许多情况下,这些考虑因素不应成为企业采用的障碍。虽然从理论上讲,用户可以避免签入指定的中央存储库,但这样做毫无意义,因为给定一个持续集成系统,如果不推送更改,就不可能根据您的代码进行构建。在不集中检查的情况下将更改推送给你的同事通常比它的价值更麻烦——当然在这种情况下除外在需要的地方,此时拥有 DVCS 非常有用。一旦指定了中央存储库,集中式版本控制系统的所有属性都可用。

In practice, these considerations should not provide a barrier to corporate adoption in many cases. While users could, in theory, avoid checking in to the designated central repository, it makes little sense to do so because, given a continuous integration system, it is impossible to get builds based on your code without pushing changes. Pushing changes to your colleagues without checking in centrally is often more trouble than it’s worth—except of course in the case where you need to, at which point having a DVCS is incredibly useful. As soon as you designate a central repository, all of the properties of a centralized version control system are available.

要记住的是,使用 DVCS,开发人员和管理员只需很少的努力就可以实现许多工作流。相反,集中式 VCS 只能通过添加颠覆底层(集中式)模型的复杂功能来支持非集中式模型(例如分布式团队、共享工作区的能力和批准工作流)。

The thing to bear in mind is that with a DVCS, many workflows are possible with very little effort on the part of developers and administrators. Conversely, centralized VCSs can only support noncentralized models (such as distributed teams, the ability to share workspaces, and approval workflows) through adding complex features that subvert the underlying (centralized) model.

使用分布式版本控制系统

Using Distributed Version Control Systems

分布式和集中式版本控制系统之间的主要区别在于,当您提交时,您是在提交到存储库的本地副本——实际上是提交到您自己的分支。为了与其他人共享您的更改,您还需要执行一组额外的步骤。为此,DVCS 有两个新操作:从远程存储库中拉取更改并将更改推送到它。

The main difference between distributed and centralized version control systems is that when you commit, you are committing to your local copy of the repository—effectively, to your own branch. In order to share your changes with others, there is an additional set of steps you need to perform. To do this, DVCSs have two new operations: pulling changes from a remote repository and pushing changes to it.

例如,这是一个典型的 Subversion 工作流程:

For example, here is a typical workflow on Subversion:

1. svn up——获取最新的修订版。

1. svn up—Get the most recent revision.

2. 写一些代码。

2. Write some code.

3. svn up — 将我的更改与中央存储库的任何新更新合并并修复所有冲突。

3. svn up—Merge my changes with any new updates to the central repository and fix any conflicts.

4. 在本地运行提交构建。

4. Run the commit build locally.

5. svn ci — 将我的更改(包括合并)检查到版本控制中。

5. svn ci—Check my changes, including my merge, into version control.

在分布式版本控制系统中,工作流程如下所示:

In a distributed version control system, the workflow looks like this:

1. hg pull——从远程仓库中获取最新的更新到你的本地仓库中。

1. hg pull—Get the latest updates from the remote repository into your local repository.

2. hg co — 从本地存储库更新本地工作副本。

2. hg co—Update your local working copy from your local repository.

3. 写一些代码。

3. Write some code.

4. hg ci — 将更改保存到本地存储库。

4. hg ci—Save your changes to your local repository.

5. hg pull — 从远程存储库获取任何新更新。

5. hg pull—Get any new updates from the remote repository.

6. hg merge——这将用合并的结果更新你的本地工作副本,但不会签入合并。

6. hg merge—This will update your local working copy with the results of the merge, but will not check in the merge.

7. 在本地运行提交构建。

7. Run the commit build locally.

8. hg ci — 这会检查合并到您的本地存储库。

8. hg ci—This checks in the merge to your local repository.

9. hg push — 将您的更新推送到远程存储库。

9. hg push—Push your updates to the remote repository.


图片

我们在这里使用 Mercurial 作为示例,因为命令语法类似于 Subversion,但原理与其他 DCVS 完全相同。

We are using Mercurial as our example here because the command syntax is similar to that of Subversion, but the principles are precisely the same with other DCVSs.


图 14.4 DVCS 工作流程(Chris Turner 绘制的图表)

Figure 14.4 DVCS workflow (diagram by Chris Turner)

图片

它看起来有点像图 14.4(每个方框代表一个修订版,箭头表示修订版的父级)。

It looks a bit like Figure 14.4 (each box represents one revision with the arrows indicating a revision’s parent).

由于第 4 步,合并过程比等效的 Subversion 更安全。这个额外的签入步骤确保即使合并不好,您也可以退回到合并前的位置并重试。这也意味着您已经记录了一个仅代表合并的更改,这样您就可以准确地看到合并做了什么,并且(假设您还没有推送您的更改)如果您稍后决定它是一个糟糕的合并,则撤消它。

The merge process is a little safer than the Subversion equivalent because of step 4. This extra check-in step ensures that even if the merge is bad, you can step back to where you were before merging and try again. It also means that you have recorded a change representing just the merge, so that you can see precisely what the merge did and (assuming you haven’t yet pushed your changes) undo it if you decide later that it was a poor one.

在执行步骤 9 以将更改发送到持续集成构建之前,您可以根据需要重复步骤 1-8 的次数。您甚至可以使用 Mercurial 和 Git 中提供的一个很棒的功能,称为变基。这使您可以更改本地存储库的历史记录,因此您可以(例如)将所有更改汇总到一个提交中。这样您就可以继续签入以保存您的更改,合并其他人所做的更改,当然还可以在本地运行您的提交套件而不影响其他用户。当您正在处理的功能完成时,您可以变基并将所有更改作为单个提交发送到主存储库。

You can repeat steps 1–8 as many times as you like before executing step 9 to send your changes to the continuous integration build. You can even use a great feature available in Mercurial and Git known as rebasing. This lets you change the history of your local repository, so you can (for example) roll up all your changes into one single commit. This way you can continue to check in to save your changes, merge changes others have made, and of course run your commit suite locally without affecting other users. When the functionality that you are working on is complete, you can rebase and send all of your changes to the master repository as a single commit.

至于持续集成,它在 DVCS 中的工作方式与在集中式版本控制系统中的工作方式完全相同。您仍然可以拥有一个中央存储库,它仍然会触发您的部署管道实现。但是,如果您愿意,DVCS 可以让您选择尝试其他几种可能的工作流程。我们将在第 79 页的“分布式版本控制系统”部分详细讨论这些内容

As for continuous integration, it works exactly the same with a DVCS as it would with a centralized version control system. You can still have a central repository, and it will still have your deployment pipeline implementation triggering off it. However, a DVCS gives you the option to try out several other possible workflows if you prefer. We discuss these in detail in the “Distributed Version Control Systems” section on page 79.


图片

在您将更改从本地存储库提交到为部署管道提供数据的中央存储库之前,您的更改不会集成。频繁提交更改是持续集成的基本实践。为了进行集成,您必须每天至少将更改推送到中央存储库一次,并且最好比这更频繁。因此,如果滥用 DVCS 的一些好处,可能会损害 CI 的有效性。

Until you commit changes from your local repository to the central repository that feeds your deployment pipeline, your changes aren’t integrated. Committing changes frequently is a fundamental practice of continuous integration. For integration to take place, you must push changes to the central repository at least once a day, and ideally much more frequently than that. So, some of the benefits of DVCS can compromise the effectiveness of CI if misused.


基于流的版本控制系统

Stream-Based Version Control Systems

IBM 的 ClearCase 不仅是大型组织中最流行的版本控制系统之一;它还在版本控制系统中引入了一种新范例:流。在本节中,我们将讨论流的工作原理以及如何与基于流的系统进行持续集成。

IBM’s ClearCase is not only one of the most popular version control systems in large organizations; it also introduced a new paradigm into version control systems: streams. In this section, we’ll discuss how streams work and how to do continuous integration with stream-based systems.

什么是基于流的版本控制系统?

What Is a Stream-Based Version Control System?

基于流的版本控制系统(如 ClearCase 和 AccuRev)旨在通过同时将更改集应用于多个分支来改善合并问题。在流范式中,分支被更强大的流概念所取代,它们具有重要的区别,即它们可以相互继承。因此,如果您对给定流应用更改,则其所有后代流都将继承这些更改。

Stream-based version control systems such as ClearCase and AccuRev are designed to ameliorate the merge problem by making it possible to apply sets of changes to multiple branches at once. In the stream paradigm, branches are replaced by the more powerful concept of streams, which have the crucial distinction that they can inherit from each other. Thus, if you apply a change to a given stream, all of its descendent streams will inherit those changes.

考虑此范例如何帮助解决两种常见情况:将错误修复应用到应用程序的多个版本以及将新版本的第三方库添加到代码库。

Consider how this paradigm helps with two common situations: applying a bugfix to several versions of your application and adding a new version of a third-party library to your codebase.

当您的发布有长期存在的分支时,第一种情况很常见。假设您需要对您的一个发布分支进行错误修复。您如何将该错误修复同时应用于代码的所有其他分支?如果没有基于流的工具,答案就是手动合并它。这是一个无聊且容易出错的过程,尤其是当您有多个不同的分支要应用更改时。使用基于流的版本控制,您只需将分支中的更改提升到需要更改的所有分支的共同祖先。然后这些分支的消费者可以更新以获得这些更改,并创建一个包含修复的新构建。

The first situation is common when you have long-lived branches for your releases. Say you need to make a bugfix to one of your release branches. How do you apply that bugfix to all other branches of your code at the same time? Without stream-based tools, the answer is to manually merge it. This is a boring and error-prone process, especially when you have several different branches to apply the change to. With stream-based version control, you simply promote the change in your branch to the common ancestor of all the branches that need the change. Consumers of these branches can then update to get these changes, and create a new build which includes the fix.

在管理第三方库或共享代码时,同样需要考虑。假设您要将图像处理库更新到新版本。每个组件都需要更新以依赖于相同的版本。使用基于流的 VCS,您可以将新版本签入到需要进行更新的每个流的祖先,并且从它继承的所有流都会选择它。

The same consideration applies when managing third-party libraries or shared code. Say you want to update an image processing library to a new version. Every component will need to update to depend on the same version. With a stream-based VCS, you can check in the new version to an ancestor of every stream that needs to take the update, and all the streams inheriting from it will pick it up.

您可以将基于流的版本控制系统视为一个联合文件系统,但文件系统形成树结构(连接的有向无环图)。因此,每个存储库都有一个根流,所有其他流都从中继承。您可以基于任何现有流创建新流。

You can think of a stream-based version control system as being rather like a union filesystem, but with filesystems forming a tree structure (a connected directed acyclic graph). So, every repository has a root stream, from which all other streams inherit. You can create new streams based on any existing stream.

图 14.5 基于流的开发

Figure 14.5 Stream-based development

图片

图 14.5的示例中,根流包含单个文件foo,修订版为 1.2,以及一个空目录。第 1 版和第 2 版流都继承自它。在版本 1 流中,可以找到根流中存在的文件,以及两个新文件:ab在版本 2 流中,存在两个不同的文件:cdfoo已经过修改,现在是 1.3 版。

In the example in Figure 14.5, the root stream contains a single file, foo, at revision 1.2, and an empty directory. Both the release 1 and release 2 streams inherit from it. In the release 1 stream, the files present in the root stream can be found, as well as two new files: a and b. In the release 2 stream, two different files are present: c and d. foo has been modified and is now at version 1.3.

两位开发人员正在他们的工作区中处理版本 2 流。开发人员 1 正在修改文件c,开发人员 2 正在修改文件d当开发人员 1 签入她的更改时,在发布 2 流中工作的每个人都将看到它们。如果文件c是版本 1 所需的错误修复,则开发人员 1 可以将文件c提升到根流,在这种情况下,它将在所有流中可见。

Two developers are working on the release 2 stream in their workspaces. Developer 1 is modifying file c, and developer 2 is modifying file d. When developer 1 checks in her changes, everybody working on the release 2 stream will see them. If file c is a bugfix that is required for release 1, developer 1 could promote file c to the root stream, in which case it would be visible from all streams.

因此,对一个流进行更改不会影响任何其他流,除非这些更改被提升。一旦提升,它们将对从原始流继承的所有其他流可见。重要的是要记住,以这种方式推动变革不会改变历史。相反,它就像在流的现有内容之上添加一个包含新更改的覆盖层。

So, making changes to one stream won’t affect any other stream, unless those changes are promoted. Once promoted, they will be visible to every other stream that inherits from the original stream. It is important to bear in mind that promoting changes in this way doesn’t change history. Rather, it’s like adding an overlay with the new changes in it on top of the existing contents of the stream.

使用流的开发模型

Development Models with Streams

在基于流的系统中,鼓励开发人员在自己的工作空间中进行开发。这样,开发人员可以执行重构、试验解决方案,并在不影响其他用户的情况下开发功能。当他们准备就绪时,他们可以推广他们的更改以供其他人使用。

In stream-based systems, developers are encouraged to develop in their own workspaces. This way, developers can perform refactorings, experiment with solutions, and develop functionality without affecting other users. When they are ready, they can promote their changes to make them available to others.

例如,您可能正在处理为特定功能创建的流。当功能完成后,您可以将该流中的所有更改推广到团队的流中,从而可以持续集成。当您的测试人员想要测试已完成的功能时,他们可以拥有自己的流,可以将所有准备好进行手动测试的功能提升到该流。然后可以将已通过测试的功能提升为发布流。

For example, you might be working on a stream you’ve created for a particular feature. When the feature is complete, you can promote all the changes in that stream to the team’s stream, which can be continuously integrated. When your testers want to test completed functionality, they could have their own stream, to which all functionality ready for manual testing can be promoted. Functionality that has passed testing can then be promoted to a release stream.

因此,大中型团队可以同时处理多项功能而不会相互影响,测试人员和项目经理可以挑选他们想要的功能。与大多数团队在发布时面临的难题相比,这是一个真正的改进。通常,创建发布涉及对整个代码库进行分支,然后稳定分支——当然,当您进行分支时,没有简单的方法来挑选您想要的部分(有关更多信息,请参见第 409 页的“发布分支”部分有关此问题的详细信息以及解决方法)。

Thus, medium and large teams can work on multiple pieces of functionality at the same time without affecting each other, and testers and project managers can cherry-pick the functionality that they want. This is a real improvement when compared to the conundrum most teams face when they come to release. Typically, creating a release involves branching the entire codebase and then stabilizing the branch—but of course when you branch, there is no simple way to cherry-pick the bits you want (see the “Branch for Release” section on page 409 for more details on this problem and ways out of it).

当然,在现实生活中,事情绝不会如此简单。功能永远不会真正相互独立,尤其是如果您的团队尽可能积极地进行重构,合并问题会在您在流之间提升大块代码时频繁发生。因此,由于以下原因,集成问题很常见也就不足为奇了:

Of course, in real life things are never quite so simple. Features are never really independent of one another and, especially if your team refactors as vigorously as it should, merge problems occur frequently as you promote large chunks of code between streams. So it is no surprise that integration issues are common as a result of:

• 复杂的合并,因为不同的团队以不同的方式更改共享代码。

• Complex merges, as different teams change shared code in different ways.

• 依赖管理问题,当新功能依赖于其他未升级功能中引入的代码时。

• Dependency management problems, when new features depend on code introduced in other features that haven’t been promoted.

• 集成问题,因为集成和回归测试在发布流中中断,因为代码处于新配置中。

• Integration issues, as integration and regression tests break on the release stream because the code is in a new configuration.

当您拥有更多团队或更多层次时,这些问题会变得更糟。这种效果通常是乘法的,因为拥有更多团队的常见反应是创建更多层。目的是隔离团队之间的影响。一家大公司报告说有五个级别的流:团队级别、领域级别、架构级别、系统级别,最后是生产级别。在投入生产之前,每个更改都必须经过每个级别。不用说,他们在发布产品时遇到了重大问题,因为这些问题经常发生在每次促销活动中。

These problems are made worse when you have more teams or more layers. This effect is often multiplicative, because a common reaction to having more teams is to create more layers. The intention is to isolate the impact of the teams on each other. One large company reported having five levels of streams: team level, domain level, architecture level, system level, and finally production level. Each change had to move through every level before getting to production. Needless to say, they faced significant problems getting releases out of the door as these issues regularly occurred upon every promotion.

重要的是要记住,不每天多次提交共享主线不利于持续集成的实践。有一些方法可以解决这个问题,但它们需要大量的纪律——而且仍然不能完全解决大中型团队所处的困境。规则经验是尽可能频繁地进行推广,并在开发人员之间共享的流上尽可能多地运行自动化测试。在这方面,这种模式类似于本章后面描述的团队分支。

It’s important to remember that not committing to a shared mainline several times a day is inimical to the practice of continuous integration. There are ways to manage this, but they require a great deal of discipline—and still won’t fully resolve the dilemma that medium and large teams find themselves in. The rule of thumb is to promote as often as possible, and run as many of the automated tests as frequently as you can on streams that are shared between developers. In this respect, this pattern is similar to branch by team, described later on in this chapter.

不过也不全是坏消息。Linux 内核开发团队使用的过程与上述非常相似,但每个分支都有一个所有者,其工作是保持该流的稳定,当然“发布流”由 Linus Torvalds 维护,他对什么非常挑剔他拉进了他的溪流。按照 Linux 内核团队的工作方式,有一个流的层次结构,Linus 的在顶层,更改由流所有者拉取,而不是向上推送。这与大多数组织中存在的结构完全相反,在这些组织中,运营或构建团队负有尝试合并所有内容的不幸职责。

It’s not all bad news though. The Linux kernel development team uses a process very similar to that described above, but each branch has an owner whose job it is to keep that stream stable, and of course the “release stream” is maintained by Linus Torvalds who is very choosy about what he pulls in to his stream. The way the Linux kernel team works, there is a hierarchy of streams with Linus’ at the top, and changes are pulled by the stream owners, rather than pushed up to them. This is quite the opposite of the structure that exists in most organizations, where the operations or build teams have the unfortunate duty of trying to merge everything.

最后,关于这种开发方式的注意事项:您不需要具有显式流支持的工具来执行此操作。事实上,Linux 内核开发团队使用 Git 来管理他们的代码,而新一代的分布式版本控制系统(如 Git 和 Mercurial)足够通用来处理这种过程——尽管没有像 AccuRev 这样的产品的一些花哨的图形工具带到桌子上。

Finally, a note about this style of development: You don’t need a tool with explicit stream support to do this. Indeed, the Linux kernel development team uses Git to manage their code, and the new breed of distributed version control systems such as Git and Mercurial are versatile enough to handle this kind of process—albeit without some of the fancy graphical tools that products like AccuRev bring to the table.

静态和动态视图

Static and Dynamic Views

ClearCase 有一个称为“动态视图”的特性。这会在文件合并到他们的流继承的流中时更新每个开发人员的视图。这意味着开发人员会立即自动获取对其流的任何更改。在更传统的静态视图中,在开发人员决定更新之前不会看到更改。

ClearCase has a feature known as “dynamic views.” This updates every developer’s view the moment a file is merged into a stream from which their stream inherits. This means that developers automatically pick up any changes to their stream immediately. In the more traditional static view, changes won’t be seen until the developer decides to update.

动态视图是在提交更改时立即获取更改的好方法,这有助于消除合并冲突并简化集成——假设开发人员经常并定期签入。然而,无论是在技术层面还是在实际的变更管理层面都存在问题。在技​​术层面上,这个特性非常慢:根据我们的经验,它大大减慢了对开发人员文件系统的访问。由于大多数开发人员经常执行编译等文件系统密集型任务,因此成本是不可接受的。更实际的是,如果您正在处理一项工作并且强加给您一个合并,它可能会打断您的思路并混淆您对问题的看法。

Dynamic views are a great way to get changes the moment they are committed, which helps to remove merge conflicts and eases integration—assuming developers check in frequently and regularly. However, there are problems both at a technical level and at a practical change management level. At the technical level, this feature is desperately slow: In our experience, it dramatically slows down access to the developer’s filesystem. As most developers are frequently performing filesystem-intensive tasks like compilation, the cost is unacceptable. More practically, if you are in the middle of a piece of work and a merge is forced on you, it can break your train of thought and confuse your picture of the problem.

与基于流的版本控制系统的持续集成

Continuous Integration with Stream-Based Version Control Systems

基于流的开发的一个声称的好处是让开发人员更容易在他们自己的私有流上工作,并承诺以后合并会更容易。从我们的角度来看,这种方法有一个根本性的缺陷:当定期(即每天不止一次)推动变化时,一切都很好,但频繁地推动变化往往会限制改变的好处方法摆在首位。如果您经常推广,更简单的解决方案同样有效或更好。如果您不经常晋升,您的团队就更有可能在发布时遇到问题。他们将花费不确定的时间让一切都成形,将每个人都认为有效的功能拼凑在一起,并修复由复杂合并引入的错误。这就是持续集成要解决的问题。

One of the purported benefits of stream-based development is to make it easier for developers to work on their own private streams, with the promise that merging will be easier later. From our perspective, this approach has a fundamental flaw: Everything is fine when changes are promoted regularly (i.e., more than once a day), but promoting that frequently tends to limit the benefits of the approach in the first place. If you are promoting frequently, simpler solutions work as well or better. If you don’t promote frequently, your team is much more likely to run into problems when it is time to release. They will spend an indeterminate amount of time getting everything into shape, patching together functionality that everybody thought was working, and fixing bugs that were introduced by the complex merges. This is the problem that continuous integration is supposed to solve.

像 ClearCase 这样的工具当然具有强大的合并功能。然而,ClearCase 也有一个完全基于服务器的模型,其中从合并到标记到删除文件的一切都需要大量的服务器活动。实际上,在 ClearCase 中将更改提升到父流需要提交者解决在同级流上发生的任何错误合并。

Tools like ClearCase certainly have powerful merging capabilities. However, ClearCase also has an entirely server-based model where everything from merging to tagging to deleting files requires a great deal of server activity. Indeed, promoting changes to a parent stream in ClearCase requires the committer to resolve any bad merges that take place on sibling streams.

我们和我们同事的 ClearCase 经验是,对于任何规模的存储库,您期望简单的操作(例如签入、删除文件和(尤其是)标记)都会花费过多的时间. 如果您想定期签入,仅此一项就为使用这些工具进行开发增加了非常可观的成本。事实上,与具有原子提交的 Accurev 不同,ClearCase 需要标记以便能够回滚到存储库的已知版本。如果您有一个经验丰富、才华横溢的 ClearCase 管理员团队帮忙,您的开发过程可能是可管理的。我们担心我们的经历普遍不好。因此,我们经常采用在开发团队中使用诸如 Subversion 之类的工具的方法,

Our ClearCase experience, and that of our colleagues, has been that with a repository of any size, operations that you would expect to be straightforward—such as checking in, deleting a file, and (especially) tagging—take an inordinate length of time. This alone adds a very significant cost to developing with these tools if you want to check in at all regularly. Indeed unlike Accurev, which has atomic commits, ClearCase requires tagging in order to be able to roll back to a known version of the repository. If you’ve got an experienced, talented team of ClearCase administrators helping out, your development process may be manageable. We are afraid that our experience has been universally bad. As a result, we have often taken the approach of using a tool such as Subversion within the development team, and doing a one-way automated merge to ClearCase periodically as a way of keeping everybody happy.

基于流的版本控制系统最重要的特性——提升变更集的能力——在持续集成方面也会造成一些问题。考虑一个应用程序,该应用程序具有用于各种单点发布的多个流。如果错误修复被提升到所有这些流的祖先,它将触发每个后代流的新构建。这可能会很快用完构建系统的所有容量。在一个在任何给定时间都有多个活动流并定期升级的团队中,构建将在每个流上持续运行。

The most important feature of stream-based version control systems—the ability to promote change sets—also causes a bit of a problem when it comes to continuous integration. Consider an application which has several streams for various point releases. If a bugfix is promoted to the ancestor of all these streams, it will trigger a new build of every single descendant stream. This could use up all the capacity of your build system very quickly. On a team which has a number of streams active at any given time, and which promotes regularly, builds will be running continuously on every stream.

有两种选择可以解决这个问题:在构建硬件或虚拟资源上花费大量资金,或者改变触发构建的方式。一种有用的策略是仅在对与部署管道关联的流进行更改时触发构建,而不是在将更改提升到其祖先时触发构建。当然,由此创建的候选发布版本仍应选择流的最新版本,包括提升到其祖先的任何更改。手动触发的构建也会导致此类更改包含在发布候选中,基础架构团队将需要确保他们在适当的时候触发手动构建,以确保在必要时创建相关的发布候选。

There are two options to deal with this problem: spend an awful lot of money on build hardware or virtual resources, or change the way builds are triggered. One useful strategy is to trigger builds only when a change is made to the stream that your deployment pipeline is associated with, not when changes are promoted to its ancestors. Of course, release candidates thus created should still pick up the latest version of the stream, including any changes promoted to its ancestors. Builds that are triggered manually would then also cause such changes to be included in the release candidate, and the infrastructure team will need to make sure that they trigger manual builds where appropriate to ensure that the relevant release candidates get created when necessary.

在主线上开发

Develop on Mainline

在本节和下一节中,我们将研究分支和合并的各种模式、它们的各种优点和缺点,以及它们适用的环境。我们将从在主线上进行开发开始,因为这种开发方法经常被忽视。事实上,它是一种极其有效的开发方式,也是唯一可以让您执行持续集成的方式。

In this and the following sections, we’ll look at various patterns for branching and merging, their various advantages and disadvantages, and the circumstances under which they are appropriate. We’ll begin with developing on mainline, because this method of development is often overlooked. In fact, it is an extremely effective way of developing, and the only one which enables you to perform continuous integration.

在这种模式下,开发人员几乎总是签入主线。很少使用分支。在主线上开发的好处包括

In this pattern, developers almost always check in to mainline. Branches are used only rarely. The benefits of developing on mainline include

• 确保所有代码持续集成

• Ensuring that all code is continuously integrated

• 确保开发人员立即采纳彼此的更改

• Ensuring developers pick up each others’ changes immediately

• 在项目结束时避免“合并地狱”和“集成地狱”

• Avoiding “merge hell” and “integration hell” at the end of the project

在这种模式下,在正常开发中,开发人员在主线上工作,每天至少提交一次代码。当面临进行复杂更改的需要时,无论是开发新功能、重构部分系统、进行深远的容量改进,还是重新构建系统层,默认情况下都不会使用分支。相反,更改是作为一组小的增量步骤来计划和实施的,这些步骤可以使测试通过,因此不会破坏现有功能。这在第 346 页的“保持您的应用程序可发布”部分中有详细描述

In this pattern, in normal development, developers work on mainline, committing code at least once a day. When faced with the need to make a complex change, be it developing new functionality, refactoring part of the system, making far-reaching capacity improvements, or rearchitecting layers of the system, branches are not used by default. Instead, changes are planned and implemented as a set of small, incremental steps that keep tests passing and so do not break existing functionality. This is described at length in the “Keeping Your Application Releasable” section on page 346.

主线开发不排除分支。相反,它意味着“所有正在进行的开发活动最终都会在某个时候出现在一条代码线上”(Berczuk,2003 年,第 54 页)。但是,只有在不必将分支合并回主线时才应创建分支,例如执行发布或提出更改时。Berczuk(同上)引用 Wingerd 和 Seiward 的主线开发优势:“90% 的 SCM ‘流程’强制执行代码线升级以弥补主线的不足”(Wingerd,1998)。

Mainline development does not preclude branching. Rather, it means “that all ongoing development activities end up on a single codeline at some time” (Berczuk, 2003, p. 54). However, branches should only be created when they won’t have to be merged back to mainline, such as when performing a release or spiking out a change. Berczuk (idem) quotes Wingerd and Seiward on the advantages of mainline development: “90% of SCM ‘process’ is enforcing codeline promotion to compensate for lack of a mainline” (Wingerd, 1998).

主线开发的后果之一是并非每次签入主线都可以发布。如果您习惯于通过分支进行功能开发或使用基于流的开发来将更改通过多个级别提升到发布流,那么这似乎是对主线开发实践的有力反驳。如果您检查主线的每个更改,您如何管理开发多个版本的大型开发人员团队?答案是软件的良好组件化、增量开发和功能隐藏。这需要在架构和开发方面更加小心,但是没有不可预测的漫长集成阶段(必须合并来自多个流的工作以创建可行的发布分支)的好处远远超过这种努力。

One of the consequences of mainline development is that not every check-in to mainline will be releasable. This may appear to be a knock-down refutation of the mainline development practice if you are used to branching for feature development or using stream-based development to promote changes up through several levels to a release stream. How do you manage large teams of developers working on multiple releases if you check every change in to mainline? The answer to this is good componentization of your software, incremental development, and feature hiding. This requires more care in architecture and development, but the benefits of not having an unpredictably long integration phase, where work from multiple streams has to be merged to create a viable release branch, far outweighs this effort.

部署管道的目标之一是允许在大型团队中频繁签入主线,这可能会导致暂时的不稳定,而仍然允许您在门外获得坚如磐石的版本。从这个意义上说,部署管道与源代码提升模型是对立的。部署管道的主要优势在于,您可以快速获得有关完全集成应用程序的每次更改的影响的反馈——这在源代码提升模型中是不可能的。这种反馈的价值在于,您可以随时确切地了解您的应用程序的状态——您不必等到集成阶段才发现您的应用程序需要数周或数月的额外工作才能发布。

One of the objectives of the deployment pipeline is to allow frequent check-ins to mainline on large teams which may result in temporary instabilities, while still allowing you to get rock-solid releases out of the door. In this sense, the deployment pipeline is antithetical to the source promotion model. The main advantage of the deployment pipeline lies in the rapid feedback you get on the effect of every change on the fully integrated application—something that is impossible in the source promotion model. The value of this feedback is that you know for sure exactly what the state of your application is at any time—you don’t have to wait until the integration phase to discover that your application needs weeks or months of extra work to be releasable.

在没有分支的情况下进行复杂的更改

Making Complex Changes without Branching

在您想要对代码库进行复杂更改的情况下,创建一个分支来进行更改,这样您就不会打断其他开发人员的工作,这似乎是最简单的操作过程。然而,在实践中,这种方法会导致多个长期存在的分支,这些分支与主线有很大的不同。合并分支到发布时间,几乎总是一个复杂的过程,需要花费不可预测的时间。每个新合并都会破坏现有功能的不同部分,然后在下一次合并发生之前稳定主线。

In a situation where you want to make a complex change to the codebase, creating a branch on which to make changes so that you don’t interrupt other developers’ work may seem the simplest course of action. However, in practice, this approach leads to multiple long-lived branches which diverge substantially from mainline. Merging branches, towards release time, is almost always a complex process that takes an unpredictable amount of time. Each new merge breaks different pieces of existing functionality and is followed by a process to stabilize the mainline before the next merge occurs.

因此,发布时间比计划的要长得多,范围更小,质量也比预期的要低。在这个模型中重构更难,除非你的代码库是松散耦合的并且遵守迪米特法则,这意味着技术债务也得到非常缓慢的偿还。这会迅速导致无法维护的代码库,从而使添加新功能、修复错误和重构变得更加困难。

As a result, releases take much longer than planned, have less scope, and are of lower quality than desired. Refactoring is harder in this model, unless your codebase is loosely coupled and obeys the Law of Demeter, which means the technical debt also gets paid off very slowly. This rapidly leads to unmaintainable codebases that make it even harder to add new functionality, fix bugs, and refactor.

简而言之,您面临着持续集成应该解决的所有问题。创建长期存在的分支从根本上与成功的持续集成策略背道而驰。

In short, you face all the problems that continuous integration is supposed to address. Creating long-lived branches is fundamentally opposed to a successful continuous integration strategy.

我们的建议不是技术解决方案,而是一种实践:始终提交到 trunk,并且每天至少执行一次。如果这似乎与对您的代码进行深远的更改不相容,那么我们谦虚地承认您可能还没有足够努力。根据我们的经验,虽然有时可能需要更长的时间将功能实现为一系列小的、增量的步骤,以保持代码处于工作状态,但好处是巨大的。拥有始终可用的代码是最基本的——我们怎么强调这种做法对于持续交付有价值的可用软件的重要性都不为过。

Our proposal is not a technical solution but a practice: Always commit to trunk, and do it at least once a day. If this seems incompatible with making far-reaching changes to your code, then we humbly submit that perhaps you haven’t tried hard enough. In our experience, although it may sometimes take longer to implement a feature as a series of small, incremental steps that keep the code in a working state, the benefits are immense. Having code that is always working is fundamental—we can’t emphasize enough how important this practice is in enabling continuous delivery of valuable, working software.

有时这种方法不起作用,但这种情况确实非常罕见,即使这样也有一些策略可以减轻影响(请参阅第 346 页上的“保持应用程序可发布”部分)。然而,即便如此,最好还是从一开始就避免这样做。通过定期签入主线的增量更改从 A 移动到 B 几乎总是正确的做法,因此请始终将其放在选项列表的首位。

There are times when this approach won’t work, but they really are very rare, and even then there are strategies that will mitigate the effects (see the “Keeping Your Application Releasable” section on page 346). However, even then it is better to avoid the need for doing this in the first place. Moving from A to B via incremental changes which are regularly checked in to mainline is almost always the right thing to do, so always put it on top of your list of options.

发布分支

Branch for Release

创建分支总是可以接受的一种情况是在发布前不久。创建分支后,发布的测试和验证是从分支上的代码完成的,而新的开发是在主线上执行的。

The one situation when it’s always acceptable to create a branch is shortly before a release. Once the branch is created, testing and validation of the release is done from code on the branch, while new development is performed on mainline.

创建一个发布分支取代了代码冻结的邪恶做法,在这种做法中,版本控制的签入在几天甚至几周内完全关闭。通过创建发布分支,开发人员可以继续签入主线,而对发布分支的更改仅针对关键错误修复。按版本的分支如图 14.2所示。

Creating a branch for release replaces the evil practice of the code freeze, in which checking in to version control is entirely switched off for days and sometimes weeks. By creating a release branch, developers can keep checking in to mainline, while changes to the release branch are made for critical bugfixes only. Branching by release is shown in Figure 14.2.

在这种模式中:

In this pattern:

• 功能始终在主线上开发。

• Features are always developed on mainline.

• 当您的代码针对特定版本完成了功能并且您想要开始处理新功能时,就会创建一个分支。

• A branch is created when your code is feature-complete for a particular release and you want to start working on new features.

• 仅在分支上提交对严重缺陷的修复,并立即将它们合并到主线中。

• Only fixes for critical defects are committed on branches, and they are merged into mainline immediately.

• 当您执行实际发布时,可选择标记此分支(如果您的版本控制系统仅管理每个文件的更改,如 CVS、StarTeam 或 ClearCase,则此步骤是强制性的)。

• When you perform an actual release, this branch is optionally tagged (this step is mandatory if your version control system only manages changes on a per-file basis, like CVS, StarTeam, or ClearCase).

激发分支发布的场景如下。开发团队需要在测试当前版本并准备部署时开始处理新功能,而测试团队希望能够修复当前版本的缺陷而不影响正在进行的新功能开发。在这种情况下,逻辑上将新功能的工作与分支上的错误修复分开是有意义的。重要的是要记住错误修复最终必须合并回主干;通常,在错误修复提交到分支后立即执行此操作是明智的。

The scenario which motivates branching for release is as follows. The development team needs to start working on new features while the current release is being tested and prepared for deployment, and the testing team wants to be able to fix defects for the current release without affecting ongoing new feature development. In this scenario, it makes sense to logically separate work on new features from bugfixing on the branch. It is important to remember that bugfixes must ultimately be merged back into trunk; in general, it is wise to do this immediately after a bugfix is committed to a branch.

在产品开发中,需要维护版本来解决在下一个版本准备就绪之前必须解决的问题。例如,安全问题需要在小版本中修复。有时很难看出功能和错误修复之间的界限,导致分支上的开发非常复杂。仍在使用该软件早期版本的付费客户可能不愿意(或无法)升级到最新版本,并且需要在旧分支上实现一些功能。团队应始终致力于尽可能减少这种情况。

In product development, maintenance releases are needed to address issues that must be fixed before the next version is ready. For example, security problems need to be fixed in point releases. Sometimes the line between features and bugfixes can be hard to see, leading to quite complex development on a branch. Paying customers still using earlier releases of the software may not be willing (or able) to upgrade to the newest version, and will need some features to be implemented on the older branch. Teams should always aim to minimize this as much as possible.

这种分支风格在真正的大型项目中效果不佳,因为大型团队或多个团队很难同时完成发布工作。在这种情况下,理想的方法是拥有一个组件化的架构,每个组件都有一个发布分支,这样团队就可以在其他团队完成他们的组件时分支并继续为他们的组件进行新的工作。如果这不可能,请查看本章后面的按团队分支模式,看看应用该模式是否更有意义。如果您需要能够挑选特征,请查看下一个模式,按特征分支。

This style of branching doesn’t work very well on really large projects because it’s hard for large teams or multiple teams to finish work on a release simultaneously. In this case, the ideal approach is to have a componentized architecture with a release branch for each component, so that teams can branch and move ahead on new work for their component while other teams are finishing their components. If this isn’t possible, take a look at the branch by team pattern later on in this chapter and see if it makes more sense to apply that pattern. If you need to be able to cherry-pick features, take a look at the next pattern, branch by feature.

重要的是,在为发布分支时,不要在发布分支之外创建更多分支。以后发布的分支应该总是脱离主线,而不是脱离现有的发布分支。从现有分支创建分支会创建一个“阶梯”结构(Berczuk,2003 年,第 150 页),这使得很难找出版本之间的通用代码。

It is important, when branching for release, not to create further branches off the release branch. Branches for later releases should always be made off mainline, not off existing release branches. Creating branches off existing branches creates a “staircase” structure (Berczuk, 2003, p. 150) which makes it hard to find out what code is common between releases.

一旦达到一定的发布频率(大约每周一次左右),分支发布就不再有意义了。在这种情况下,简单地推出软件的新版本而不是在发布分支上打补丁更便宜也更容易。相反,您的部署管道会记录执行了哪些发布、何时发布以及它们来自版本控制中的哪个修订版。

Once you achieve a certain frequency of releases, around once a week or so, it no longer makes sense to branch for release. In this scenario, it’s cheaper and easier to simply put out a new version of the software instead of patching on the release branch. Instead, your deployment pipeline keeps a record of which releases were performed, when, and what revision in version control they came from.

按功能分支

Branch by Feature

这种模式旨在使大型团队更容易同时处理功能,同时保持主线处于可发布状态。每个故事或功能都在单独的分支上开发。只有当一个故事被测试人员接受后,它才会被合并到主线,以确保主线始终是可发布的。

This pattern is designed to make it easier for large teams to work simultaneously on features while keeping mainline in a releasable state. Every story or feature is developed on a separate branch. Only after a story is accepted by testers, it is merged to mainline so as to ensure that mainline is always releasable.

这种模式通常是出于希望保持主干始终可发布的愿望,因此在分支上进行所有开发,这样您就不会干扰其他开发人员或团队。许多开发人员不喜欢公开他们的工作,直到他们完全完成。此外,如果每次提交都代表一个完整的功能或一个完整的错误修复,它会使版本控制历史在语义上更加丰富。

This pattern is generally motivated by the desire to keep the trunk always releasable, and therefore do all of the development on a branch so you don’t interfere with other developers or teams. Many developers don’t like to have their work exposed and publicly available until they are completely done. In addition, it makes version control history more semantically rich if each commit represent a complete feature or a complete bugfix.

这种模式的工作有一些先决条件,更不用说很好了。

There are some prerequisites for this pattern to work at all, let alone well.

• 来自主线的任何更改必须每天合并到每个分支。

• Any changes from mainline must be merged onto every branch on a daily basis.

• 分支必须是短期的,最好少于几天,绝不能超过一次迭代。

• Branches must be short-lived, ideally less than a few days, never more than an iteration.

• 任何时候存在的活动分支数必须限制在游戏中的故事数。除非代表他们以前的故事的分支被合并回主线,否则任何人都不应该开始一个新的分支。

• The number of active branches that exist at any time must be limited to the number of stories in play. Nobody should start a new branch unless the branch representing their previous story is merged back to mainline.

• 考虑让测试人员在故事合并之前接受故事。只允许开发人员在故事被接受后合并到主干。

• Consider having testers accept stories before they are merged. Only allow developers to merge to trunk once a story has been accepted.

• 必须立即合并重构以最小化合并冲突。此约束很重要但可能很痛苦,并且进一步限制了此模式的实用性。

• Refactorings must be merged immediately to minimize merge conflicts. This constraint is important but can be painful, and further limits the utility of this pattern.

• 技术负责人的部分职责是负责保持主干可释放。技术负责人应该审查所有合并,也许以补丁形式。技术负责人有权拒绝可能会破坏主干的补丁。

• Part of the technical lead’s role is to be responsible for keeping the trunk releasable. The tech lead should review all merges, perhaps in patch form. The tech lead has the right to reject patches that may potentially break the trunk.

由于合并的组合问题,拥有许多长期存在的分支是不好的。如果你有四个分支,它们中的每一个都只会从主线合并,而不是相互合并。所有四个分支都发散。只需要两个分支在紧密耦合的代码库中执行重构,当其中一个分支合并时,整个团队就会陷入停顿。值得重申的是,分支从根本上与持续集成是对立的。即使你对每个分支都进行了持续集成,实际上并没有解决集成的问题,因为您实际上并没有整合您的分支机构。最接近真正持续集成的方法是让您的 CI 系统将每个分支合并到一个假设的“主干”中,该“主干”代表如果每个人都合并时主干的样子,并针对它运行所有自动化测试。这是我们的一种做法在第 79 页的分布式版本控制系统的上下文中进行描述当然,这样的合并在大多数情况下很可能会失败,这很好地说明了这个问题。

Having many long-lived branches is bad because of the combinatorial problem of merging. If you have four branches, each of them will only be merging from mainline, not with each other. All four branches are diverging. It only takes two branches performing a refactoring in a tightly coupled codebase to bring the entire team to a halt when one of them merges. It bears repeating that branching is fundamentally antithetical to continuous integration. Even if you perform continuous integration on every branch, it doesn’t actually address the problem of integration, since you’re not in fact integrating your branch. The nearest you can get to true continuous integration is to have your CI system merge every branch into a hypothetical “trunk” that represents what the trunk would look like if everybody were to merge, and run all automated tests against that. This is a practice we describe in the context of distributed version control systems on page 79. Of course, such a merge would likely fail most of the time, which nicely demonstrates the problem.

分布式版本控制系统 (DVCS) 的设计正是考虑到了这种模式,并且使得合并到主干和从主干合并以及创建针对头部的补丁变得异常容易。使用 GitHub(例如)的开源项目可以大大提高开发速度,方法是让用户可以轻松地对存储库进行分支以添加功能,然后让提交者可以从中提取分支。但是,开源项目的一些关键属性使它们特别适合这种模式。

Distributed version control systems (DVCSs) are designed with exactly this kind of pattern in mind, and make it absurdly easy to merge to and from trunk and create patches against head. Open source projects that use GitHub (for example) can achieve large gains in development speed by making it easy for users to branch a repository to add a feature and then make the branch available to a committer to pull from. However, there are some key attributes of open source projects that make them especially suitable for this pattern.

• 尽管很多人都可以为它们做出贡献,但它们由相对较小的经验丰富的开发人员团队管理,他们拥有接受或拒绝补丁的最终权力。

• Although many people can contribute to them, they are managed by a relatively small team of experienced developers who have the ultimate power to accept or reject patches.

• 发布日期相对灵活,允许开源项目的提交者在拒绝次优补丁方面有很大的自由度。虽然商业产品也可能如此,但这不是常态。

• Release dates are relatively flexible, allowing the committers of open source projects a wide degree of latitude in rejecting suboptimal patches. While this can also be true of commercial products, it is not the norm.

因此,在开源世界中,这种模式可能非常有效。它还可以用于核心开发团队规模小且经验丰富的商业项目。它可以在较大的项目中工作,但仅适用于以下条件:交付团队被分成几个小团队,每个小团队都有经验丰富的领导者;整个团队致力于经常检查并与主线集成;并且交付团队不会受到过度发布的压力,这可能会导致做出次优决策。

Therefore, in the open source world this pattern can be very effective. It can also work for commercial projects where the core development team is small and experienced. It can work in larger projects, but only where the following conditions apply: The codebase is modular and well factored; the delivery team is split into several small teams, each with experienced leaders; the whole team is committed to checking in and integrating with mainline frequently; and the delivery team is not subject to undue pressure to release which might lead to suboptimal decision making.

我们对推荐这种模式持谨慎态度,因为它与商业软件开发中最常见的反模式之一密切相关。在这个邪恶但极为普遍的镜像宇宙中,开发人员分支创建功能。该分支长期处于隔离状态。与此同时,其他开发商正在创建其他分支机构。当接近发布时间时,所有分支都会合并到主干中。

We are cautious about recommending this pattern because it is so closely related to one of the most common antipatterns of commercial software development. In this evil, but extremely common, mirror universe, developers branch to create features. This branch stays isolated for a long time. Meanwhile, other developers are creating other branches. When it comes close to release time, all the branches get merged into trunk.

在这一点上,还有几周的时间,整个测试团队基本上一直在摆弄他们的拇指,发现主干上的奇怪错误突然有一个完整的版本值得发现的集成和系统级错误,以及所有尚未发现的功能级错误,因为没有人费心让测试人员在集成之前正确检查分支。无论如何,测试人员最好不要打扰,因为开发团队没有时间在发布日期之前修复许多错误。管理人员、测试人员、

At this point, with a couple of weeks to go, the entire testing team that has been basically twiddling their thumbs finding the odd bug on trunk suddenly has a whole release worth of integration and system-level bugs to discover, as well as all the feature-level bugs which have not yet been found because nobody bothered to have the testers check the branches properly before they got integrated. The testers may as well not bother anyway, because the development team doesn’t have the time to fix many of the bugs before the release date. Management, testers, and the development team will spend a week or four in a fury of reprioritizing and fighting to get critical bugs fixed before the whole sorry mess is dumped on the operations team to somehow get it into production or otherwise make it available to users—who are not thrilled to be on the receiving end of the resulting dog’s dinner.

这股力量非常强大,需要一支纪律严明的队伍才能避免这个问题。使用此模式来推迟确保您的应用程序处于可发布状态的痛苦太容易了。我们已经看到即使是小型的、经验丰富的忍者级敏捷团队也会把这种模式搞得一团糟,所以我们其他人希望渺茫。你应该始终从“在主线上开发”模式开始,然后,如果你想尝试按功能分支,严格按照上述规则进行。Martin Fowler 写了一篇文章,生动地展示了按特性分支的风险 [bBjxbS],特别是它与持续集成的不稳定关系。在第 79 页的“分布式版本控制系统”部分中有更多关于使用 DVCS 和持续集成的信息

This force is very strong, and it will take an extremely disciplined team to avoid this problem. It is all too easy to use this pattern to defer the pain of making sure your application is in a releasable state. We have seen even small, experienced, ninja-level agile teams mess this pattern up, so there is little hope for the rest of us. You should always start with the “develop on mainline” pattern and then, if you want to try branching by feature, proceed rigidly according to the rules above. Martin Fowler wrote an article that demonstrates vividly the risks of branching by feature [bBjxbS], in particular its uneasy relationship with continuous integration. There is more on using DVCS with continuous integration in the “Distributed Version Control Systems” section on page 79.

总的来说,您需要非常确定这种模式的好处超过可观的开销,并且它不会在发布时间到来时导致崩溃。您还应该考虑其他模式,例如通过抽象分支使用组件而不是分支来管理扩展,或者只是应用扎实的工程学科来使每个更改都小而增量,并定期检查到主线。上一章详细描述了所有这些实践。

Overall, you need to be pretty certain that the benefits of this pattern outweigh the considerable overhead, and that it will not lead to meltdown when release time arrives. You should also consider other patterns, such as branch by abstraction using components instead of branches to manage scaling, or just apply the solid engineering discipline to make every change small and incremental and check in to mainline regularly. All of these practices are described at length in the previous chapter.

值得强调的是,按功能分支实际上是持续集成的对立面,我们关于如何使其工作的所有建议都只是为了确保合并时间的痛苦不会太可怕。首先避免疼痛要简单得多。当然,就像软件开发中的所有“规则”一样,也有一些例外情况可能有意义,例如开源项目或使用分布式版本控制系统的经验丰富的开发人员组成的小团队。但是,请注意,当您采用这种模式时,您是在“用剪刀跑步”。

It is worth emphasizing that branching by feature is really the antithesis of continuous integration, and all of our advice on how to make it work is only about ensuring that the pain isn’t too horrible come merge time. It is much simpler to avoid the pain in the first place. Of course, like all “rules” in software development, there are exceptions where this may make sense, such as open source projects or small teams of experienced developers working with distributed version control systems. However, be aware that you are “running with scissors” when you adopt this pattern.

按团队划分

Branch by Team

这种模式试图解决在保持主线的情况下让大型开发人员团队处理多个工作流的问题可以随时发布。与按功能分支一样,此模式的主要目的是确保主干始终可释放。为每个团队创建一个分支,只有当分支稳定时才合并到主干中。对任何给定分支的每次合并都应立即拉入每个其他分支。

This pattern is an attempt to address the problem of having a large team of developers working on multiple work streams while still maintaining a mainline that can always be released. As with branch by feature, the main intent of this pattern is to ensure that the trunk is always releasable. A branch is created for every team, and merged into trunk only when the branch is stable. Every merge to any given branch should immediately be pulled into every other branch.

图 14.7 按团队划分的分支

Figure 14.7 Branch by team

图片

这是按团队分支的工作流程。7

Here is the workflow for branching by team.7

1. 创建小团队,每个团队在自己的分支机构工作。

1. Create small teams, each working on its own branch.

2. 一旦完成一个功能/故事,分支就稳定下来并合并到主干。

2. Once a feature/story is completed, the branch is stabilized and merged to trunk.

3. 主干上的任何更改每天都会合并到每个分支。

3. Any changes on trunk get merged to every branch daily.

4. 每次在分支机构签到时都会运行单元和验收测试。

4. Unit and acceptance tests are run on every check-in on the branch.

5. 每次将分支合并到主干中时,包括集成测试在内的所有测试都会在主干上运行。

5. All tests, including integration tests, are run on trunk every time a branch is merged into it.

当您让开发人员直接检查主干时,很难确保您始终可以按照迭代开发方法的要求定期发布您的工作。如果您有多个团队在处理故事,则主干几乎总是包含未完成的工作,这会阻止应用程序按原样发布,除非您严格遵守第 346 页“保持应用程序可发布”部分中的规则。在这种模式下,开发人员只签入他们团队的分支。当所有正在处理的功能都完成时,这个分支只会合并到主干。

When you have developers checking directly into trunk, it is hard to ensure that you can always release your work at regular intervals, as required by iterative development methods. If you have several teams working on stories, the trunk will almost always contain half-completed work that prevents the application from being released as is, unless you are disciplined about following the rules in the “Keeping Your Application Releasable” section on page 346. In this pattern, developers only check in to their team’s branch. This branch is only merged to trunk when all the features being worked on are complete.

当您有几个小的、相对独立的团队在系统的功能独立区域工作时,此模式适用。至关重要的是,每个分支机构都需要有一个所有者负责定义和维护其政策,包括管理谁在分支机构签到。如果您想入住一家分店,您必须找到一家不会违反其政策的分店。否则,您必须创建一个新分支。

This pattern works when you have several small, relatively independent teams working on functionally independent areas of the system. Crucially, every branch needs to have an owner responsible for defining and maintaining its policy, including governing who checks in to the branch. If you want to check in to a branch, you must find one whose policy your check-in will not violate. Otherwise, you must create a new branch.

此模式旨在将主干保持在可释放状态。然而,这个模式中的每个分支都面临着完全相同的问题——它只有在“稳定”时才能合并到主干中。如果分支可以合并到主干中而不破坏任何自动化测试(包括验收和回归测试),则实际策略认为该分支是稳定的。因此,每个分支实际上都需要自己的部署管道,以便团队可以确定哪些构建是好的,从而可以在不违反政策的情况下将哪些版本的源代码合并到主线。任何这样的版本都应该在构建开始之前将最新版本的主线合并到其中,以确保将分支合并到主线不会导致主线构建失败。

This pattern aims to maintain the trunk in a releasable state. However, each branch in this pattern faces exactly the same problem—it can only be merged into trunk when it is “stable.” The actual policy considers a branch stable if it can be merged into trunk without breaking any of the automated tests, including acceptance and regression tests. Thus each branch effectively needs its own deployment pipeline, so that the team can determine which builds are good and thus which versions of the source code can be merged to mainline without violating the policy. Any such version should have had the latest version of mainline merged into it before the build is kicked off, so as to ensure that merging the branch to mainline will not cause the mainline build to fail.

从 CI 的角度来看,这种策略有一些缺点。一个根本问题是,此策略下的工作单元范围是整个分支,而不仅仅是特定的更改。换句话说,你不能将单个更改合并到主线——你必须合并整个分支,否则无法知道你是否违反了主线策略。如果团队在合并到主干后发现了一个错误,并且分支中还有其他更改,他们不能只合并修复。在这种情况下,团队要么必须再次稳定分支,要么创建另一个分支来进行修复。

From a CI perspective, this strategy has some drawbacks. One fundamental problem is that the unit of work under this strategy is scoped to a whole branch, not just a particular change. In other words, you can’t merge a single change to mainline—you have to merge the whole branch, otherwise there is no way of knowing whether you have violated the mainline policy. If the team discovers a bug after it has merged to trunk, and there are other changes in the branch, they can’t just merge the fix. In this situation, the team would either have to get the branch stable again, or create yet another branch just for the fixes.

其中一些问题可以通过使用分布式版本控制系统 (DVCS) 来缓解。Linux 内核开发团队使用了这种模式的一个版本,将操作系统不同部分(例如调度程序和网络堆栈)的逻辑分支保存在独立的存储库中。DVCS 能够将选定的变更集从一个存储库发送到另一个存储库,这一过程称为 cherry-picking。这意味着您不必总是合并整个分支,而是可以只合并您想要的功能。现代 DVCS 还具有复杂的变基设施,因此您可以追溯地将补丁应用到以前的变更集并将它们捆绑起来。所以如果你在你的补丁中发现了一个错误,你可以将错误修复添加到补丁中,通过你的管道运行这个版本来验证它不会破坏主线,并合并附加补丁。DVCS 的使用将这种模式从我们不推荐的模式转变为我们在某些情况下可能推荐的模式,前提是团队定期合并到主线。

Some of these problems can be mitigated through the use of a distributed version control system (DVCS). The Linux kernel development team uses a version of this pattern, keeping logical branches for different parts of the operating system—the scheduler and the networking stack, for example—in independent repositories. DVCSs have the ability to send selected changesets from one repository to another, a process known as cherry-picking. This means that rather than always merging the whole branch, you can merge just the features you want. Modern DVCSs also have sophisticated rebasing facilities so that you can retroactively apply patches to previous changesets and bundle them up. So if you discover a bug in your patch, you can add the bugfix to the patch, run this version through your pipeline to verify it won’t break the mainline, and merge the additional patch. The use of a DVCS turns this pattern from one we would not recommend to one that we might recommend under certain circumstances, provided the teams merge to mainline on a regular basis.

如果合并不够频繁,则此模式会遇到与整个团队不直接签入主干的所有模式相同的缺点:真正的持续集成会受到损害。这意味着总是存在频繁、严重的合并冲突的风险。出于这个原因,Kniberg 建议每个团队在故事完成时合并到主干,并且每天从主干合并。然而,即使有这些附带条件,总会有一个由于需要使每个分支与主线同步,因此需要保持一致的开销——可能是一个很大的开销。如果分支之间存在很大分歧,例如通过对紧密耦合的代码库执行重构,团队将需要尽快同步这些更改以避免合并冲突。反过来,这意味着对稳定版本的分支执行重构,因此它们可以立即合并到主线。

If merges aren’t sufficiently frequent, this pattern suffers from the same drawback as every pattern where the whole team does not check in directly to trunk: True continuous integration is compromised. This means there is always the risk of frequent, serious merge conflicts. For this reason, Kniberg recommends that every team merges to trunk whenever a story is completed, and merges from trunk every day. However, even with these provisos, there will always be a consistent overhead—possibly a substantial one—due to the need to keep every branch synchronized with mainline. If the branches diverge substantially from each other, by performing a refactoring on a tightly coupled codebase for example, teams will need to synchronize these changes as soon as possible to avoid merge conflicts. This, in turn, means performing refactorings against a stable version of the branch, so they can be merged to mainline immediately.

实际上,这种模式与按特征分支没有什么不同。它的优点是分支较少,因此集成发生得更频繁——至少在团队层面是这样。它的缺点是分支分叉的速度要快得多,因为整个团队都在检查每个分支。因此合并会变得比按功能分支要复杂得多。主要的风险是团队在将更改合并到主线和从主线合并方面没有足够的纪律。团队分支会迅速从主线和彼此之间发生分歧,合并冲突很快就会变得非常痛苦。我们在现实生活中看到过这种模式,这几乎是不可避免的结果。

In practice, this pattern is not dissimilar to branch by feature. Its advantage is that there are fewer branches, so integration happens more frequently—at the team level at least. Its disadvantage is that branches diverge much more rapidly, because a whole team is checking in to each branch. Thus merges can become significantly more complex than they would be if branching by feature. The main risk is that teams are not sufficiently disciplined about merging changes to and from the mainline. Team branches will diverge rapidly from the mainline and from each other, and merge conflicts can quickly become extremely painful. Where we have seen this pattern in real life, this was, almost inevitably, the outcome.

正如我们在第 346 页的“保持您的应用程序可发布”部分中已经详细描述的那样,我们建议采用增量开发方法以及功能隐藏作为保持您的应用程序可发布的最佳方式,即使您在开发过程中也是如此新功能。总的来说,虽然它需要更多的纪律,但它比管理多个分支、必须不断合并,并且没有真正的持续集成所提供的关于您的更改对整个应用程序的影响的快速反馈要低得多。

As we have already described at length in the “Keeping Your Application Releasable” section on page 346, we recommend an incremental approach to development along with feature hiding as the best way to keep your application releasable even when you’re in the middle of developing new features. In general, while it requires more discipline, it is considerably less risky than managing several branches, having to constantly merge, and not having the rapid feedback on the effects of your changes on the whole application that true continuous integration provides.

但是,如果您正在处理大型单体代码库,则此模式(连同抽象分支)可以构成转向松散耦合组件策略的有用部分。

However, it if you are working on a large, monolithic codebase, this pattern (along with branch by abstraction) can form a useful part of a strategy of moving to loosely coupled components.

概括

Summary

有效控制您在软件开发过程中创建和依赖的资产对于任何规模的项目的成功都是必不可少的。版本控制系统的演变和围绕它们的配置管理实践是软件行业历史的重要组成部分。现代版本控制系统的复杂性和它们的易用性表明了它们对现代基于团队的软件开发的核心重要性。

Effective control of the assets that you create and depend upon in the course of software development is essential for the success of a project of any size. The evolution of version control systems and the configuration management practices that surround them is an important part of the history of the software industry. The sophistication of modern version control systems and their easy availability is a statement of their central importance to modern team-based software development.

我们在这个可以说是无关紧要的主题上花费大量时间的原因有两个:首先,版本控制模式是您设计部署管道的方式的核心。其次,根据我们的经验,糟糕的版本控制实践是快速、低风险发布的最常见障碍之一。这些版本控制系统的一些强大功能的应用方式可能会危及安全、可靠、低风险软件发布的机会。了解可用的功能、选择正确的工具并适当地使用它们是成功的软件项目的重要属性。

The reason we spend so much time on this arguably tangential topic is twofold: Firstly, version control patterns are central to the way you design your deployment pipeline. Secondly, it has been our experience that poor version control practices are one of the most common barriers to fast, low-risk releases. Some of the powerful features of these version control systems can be applied in ways that endanger the chances of safe, reliable, low-risk software releases. Understanding the available features, picking the correct tools, and using them appropriately is an important attribute of a successful software project.

我们花了一些时间比较不同的版本控制系统范例:标准的集中式模型、分布式模型和基于流的模型。我们相信,尤其是分布式版本控制系统将继续对软件交付方式产生巨大的积极影响。但是,仍然可以使用标准模型创建高效流程。对于大多数团队来说,更重要的考虑因素是使用哪种策略进行分支。

We have spent some time comparing different version control system paradigms: the standard centralized model, the distributed model, and the stream-based model. We believe that distributed version control systems in particular will continue to have a massive positive impact on the way software is delivered. However, it is still possible to create an efficient process using the standard model. For most teams, a more important consideration is which strategy to use for branching.

在持续集成的愿望和分支的愿望之间存在着根本的紧张关系。每次您决定在基于 CI 的开发系统中进行分支时,您都会在某种程度上做出妥协。使用哪种模式的问题应该基于为您的团队和软件项目确定最佳过程。一方面,CI 的绝对观点是每一个变化都应该尽快提交到 trunk 上。主干始终是系统状态的最完整和最新的声明,因为您将从它进行部署。更改与主干分开的时间越长——无论技术是什么,或者合并工具有多复杂——当最终合并发生时,出现问题的风险就越大。另一方面,有一些因素,

There is a fundamental tension between the desire for continuous integration and the desire to branch. Every time you make a decision to branch in a CI-based development system, you compromise to some degree. The question of which pattern to use is a choice that should be based on identifying the optimal process for your team and your software project. On one hand, an absolute view of CI says that every change should be committed as soon as possible to trunk. The trunk is always the most complete and up-to-date statement of the state of your system, because you will deploy from it. The longer the changes are kept separate from trunk—no matter what the technology is, or how sophisticated the merge tools are—the greater the risk that, when the eventual merge takes place, there will be a problem. On the other hand, there are factors, such as bad networks, slow builds, or convenience, that make it more efficient to branch.

本章提出了一系列选项来应对这样的情况,在这种情况下,开发团队在某种程度上妥协 CI 会更有效。但是,重要的是,每次分支时,您都要认识到与之相关的成本。该成本伴随着风险的增加而增加,而将风险最小化的唯一方法是努力确保无论出于何种原因创建的任何活动分支都应每天或更频繁地合并回主线。没有这个,流程就不能再被认为是基于持续集成的。

This chapter has presented a series of options to cope with such situations in which it is more efficient for a development team to compromise CI to some extent. However, it is important that every time you branch, you recognize that there is a cost associated with it. That cost comes in increased risk, and the only way to minimize that risk is to be diligent in ensuring that any active branch, created for whatever reason, should be merged back to mainline daily or more frequently. Without this, the process can no longer be considered to be based on continuous integration.

正如我们所说,我们可以在没有任何警告的情况下推荐分支的唯一原因是为了发布、尖峰和在没有其他合理方法使您的应用程序达到可以通过以下方式进行进一步更改的点时的极端情况其他方法。

As we have said, the only reasons to branch we can recommend without any caveats are for releases, for spiking, and in extremis when there is no other reasonable way to get your application to a point where it will be possible to make further changes by other methods.

第 15 章管理持续交付

Chapter 15. Managing Continuous Delivery

介绍

Introduction

本书主要面向从业者。然而,实施持续交付不仅仅涉及购买一些工具和进行一些自动化工作。这取决于参与交付的每个人之间的有效协作、执行发起人的支持以及实地人员做出改变的意愿。本章旨在提供有关如何在您的组织内进行持续交付的指导。首先,我们提出了一个用于配置和发布管理的成熟度模型。接下来,我们将探讨如何规划项目的生命周期,包括发布。然后我们描述了一种在软件项目中构建和发布的风险管理方法。最后,我们将了解部署中涉及的常见组织风险和反模式,以及可帮助您避免它们的最佳实践和模式。

This book is mainly aimed at practitioners. However, implementing continuous delivery involves more than just buying some tools and doing some automation work. It depends on effective collaboration between everyone involved in delivery, support from executive sponsors, and willingness of people on the ground to make changes. This chapter is written to provide guidance on how to make continuous delivery work within your organization. First, we present a maturity model for configuration and release management. Next, we will explore how to plan your project’s lifecycle, including release. Then we describe an approach to risk management of build and release in software projects. Finally, we take a look at the common organizational risks and antipatterns involved in deployment, along with best practices and patterns to help you to avoid them.

在我们开始之前,我们想展示持续交付的整体价值主张。持续交付不仅仅是一种新的交付方法。这是运行依赖于软件的业务的全新范例。要理解为什么会这样,我们需要审视公司治理核心的根本张力。

Before we get started, we wanted to present the overall value proposition of continuous delivery. Continuous delivery is more than just a new delivery methodology. It is a whole new paradigm for running a business that depends on software. To understand why this is so, we need to examine a fundamental tension at the heart of corporate governance.

CIMA 将企业治理定义为“董事会和执行管理层行使的一系列职责和实践,其目标是提供战略方向、确保目标实现、确定风险得到适当管理以及验证组织资源得到负责任地使用。 ” 它还将公司治理与企业治理区分开来,企业治理关注合规——换言之,合规、保证、监督和负责任、透明的管理——关注业务绩效和价值创造。

CIMA defines enterprise governance as “the set of responsibilities and practices exercised by the board and executive management with the goal of providing strategic direction, ensuring that objectives are achieved, ascertaining that risks are managed appropriately, and verifying that the organization’s resources are used responsibly.” It goes on to differentiate corporate governance, which is concerned with conformance—in other words, compliance, assurance, oversight, and responsible, transparent management—from business governance, concerned with the performance of the business and value creation.

一方面,企业希望尽快推出有价值的新软件,以保持收入增长。另一方面,负责公司治理的人员希望确保组织了解可能导致企业亏损或倒闭的任何风险,例如违反适用法规,并确保制定流程来管理这些风险。

On the one hand, the business wants to get valuable new software out of the door as fast as possible in order to keep increasing revenue. On the other hand, people responsible for corporate governance want to ensure that the organization understands any risks that could lead to the business losing money or being shut down, such as violation of applicable regulations, and that processes are in place to manage these risks.

虽然企业中的每个人最终都有一个共同的目标,但绩效和一致性是经常会发生冲突的力量。这可以从承受着尽快交付压力的开发团队与将任何变更视为风险的运营团队之间的关系中看出。

While everybody in the business ultimately has a shared goal, performance and conformance are forces that can often come into conflict. This can be seen in the relationship between development teams, who are under pressure to ship as quickly as possible, and operations teams who treat any change as a risk.

我们认为组织的这两个部分没有参与零和游戏。可以同时实现一致性和性能。这个原则正是持续交付的核心。部署管道旨在通过确保交付团队获得有关其应用程序生产准备情况的持续反馈来实现性能。

We contend that these two parts of the organization are not participating in a zero sum game. It is possible to achieve both conformance and performance. This principle is right at the heart of continuous delivery. The deployment pipeline is designed to achieve performance by ensuring the delivery teams get constant feedback on the production-readiness of their application.

它还旨在通过使交付过程透明化来帮助团队实现一致性。IT 和业务都可以随时试用该应用程序,也许是为了测试一些新功能,方法是通过自助服务将应用程序部署到 UAT 环境。出于审计目的,管道提供了一个记录系统,可以准确记录每个应用程序的哪些版本已经通过了交付过程的哪些部分,以及从每个环境中的内容追溯到它在版本控制中的修订的能力。这个领域的许多工具都提供了锁定谁可以做什么的工具,以便部署只能由授权人员执行。

It is also designed to help teams achieve conformance by making the delivery process transparent. Both IT and the business can try out the application at any time, perhaps to test some new feature, by self-servicing a deployment of the application to a UAT environment. For audit purposes, the pipeline provides a system of record as to exactly which versions of each application have been through which parts of the delivery process, and the ability to trace back from what’s in every environment to the revision it came from in version control. Many of the tools in this space provide the facility to lock down who can do what, so that deployments can only be performed by authorized people.

本书中的实践,特别是构建、测试和部署过程的增量交付和自动化,都是为了帮助管理发布新版本软件的风险而设计的。全面的测试自动化为应用程序的质量提供了高度的信心。部署自动化提供了发布新更改和按下按钮退出的能力。使用相同流程部署到每个环境和自动化环境、数据和基础设施管理等实践旨在确保发布流程经过彻底测试,人为错误的可能性最小化,以及任何问题——无论是功能性的、非功能性的、或配置相关的——在发布之前就被发现了。

The practices in this book, in particular incremental delivery and automation of the build, testing, and deployment process, are all designed to help manage the risk of releasing new versions of the software. Comprehensive test automation provides a high level of confidence in the quality of the application. Deployment automation provides the ability to release new changes and back out at the press of a button. Practices such as using the same process to deploy into every environment and automated environment, data, and infrastructure management are designed to ensure that the release process is thoroughly tested, the possibility for human error is minimized, and any problems—whether functional, nonfunctional, or configuration-related—are discovered well before release.

使用这些实践,即使是拥有复杂应用程序的大型组织也可以快速可靠地交付其软件的新版本。这意味着企业不仅可以更快地获得投资回报,而且可以降低风险,并且不会产生长开发周期的机会成本——或者更糟糕的是,交付不符合目的的软件。与精益制造打个比方,不经常交付的软件就像储存在仓库中的库存。制造它需要花钱,但并没有给你带来任何收益——事实上,储存它需要花钱。

Using these practices, even large organizations with complex applications can deliver new versions of their software rapidly and reliably. That means not only that businesses can get a faster return on their investment, but that they can do so with reduced risks and without incurring the opportunity cost of long development cycles—or worse, delivering software that is not fit for purpose. To use an analogy with lean manufacturing, software that is not being delivered frequently is like inventory stored up in a warehouse. It has cost you money to manufacture, but is not making you any money—indeed, it is costing you money to store it.

配置和发布管理的成熟度模型

A Maturity Model for Configuration and Release Management

在讨论治理主题时,对组织变革的目标有一个清晰的认识是非常有用的。经过多年的咨询工作——这个职业让我们有机会看到许多不同的组织并了解他们工作实践的细节——我们和我们的同事已经提炼出一个模型来评估我们工作的组织。这个模型有助于识别组织在其过程和实践的成熟度方面所处的位置,并定义了组织可以努力改进的进程。

In discussing the topic of governance, it is extremely useful to have a clear view of the objectives of organizational change. Over many years of working in consultancy—an occupation that gives an opportunity to see many different organizations and understand the detail of their working practices—we and our colleagues have distilled a model for evaluating the organizations that we work in. This model helps to identify where an organization stands in terms of the maturity of its processes and practices and defines a progression that an organization can work through to improve.

图 15.1 成熟度模型

Figure 15.1 Maturity model

图片

特别是,我们一直在谨慎地处理在整个组织中交付软件所涉及的所有角色,以及他们如何协同工作。图 15.1显示了模型。

In particular, we have been careful to address all the roles involved in the delivery of software across an organization, and how they work together. Figure 15.1 shows the model.

如何使用成熟度模型

How to Use the Maturity Model

最终目标是让您的组织得到改进。你想要的结果是:

The ultimate aim is for your organization to improve. The outcomes you want are:

• 缩短周期时间,以便您可以更快地为您的组织创造价值并提高盈利能力。

• Reduced cycle time, so that you can deliver value to your organization faster and increase profitability.

• 减少缺陷,这样您就可以提高效率并减少支持费用。

• Reduced defects, so that you can improve your efficiency and spend less on support.

• 提高软件交付生命周期的可预测性,使规划更加有效。

• Increased predictability of your software delivery lifecycle to make planning more effective.

• 能够采取并保持遵守您所遵守的任何监管制度的态度。

• The ability to adopt and maintain an attitude of compliance to any regulatory regime that you are subject to.

• 有效确定和管理与软件交付相关的风险的能力。

• The ability to determine and manage the risks associated with software delivery effectively.

• 由于更好的风险管理和更少的软件交付问题而降低了成本。

• Reduced costs due to better risk management and fewer issues delivering software.

我们相信这个成熟度模型可以作为指南,帮助您实现所有这些成果。我们一如既往地建议您应用戴明循环——计划、执行、检查、行动。

We believe that this maturity model can act as a guide to help you achieve all of these outcomes. We recommend, as ever, that you apply the Deming cycle—plan, do, check, act.

1. 使用模型对您组织的配置和发布管理成熟度进行分类。您可能会发现组织的不同部分在每个不同类别中达到不同的级别。

1. Use the model to classify your organization’s configuration and release management maturity. You may find that different parts of your organization achieve different levels in each of the different categories.

2. 选择一个领域来关注你的不成熟特别痛苦的地方。价值流图将帮助您确定需要改进的领域。本书将帮助您了解每项改进带来的好处以及如何实施。您应该决定哪些改进对您的组织有意义,估计它们的成本和收益,并确定优先级。您应该定义验收标准以指定您期望的结果以及如何衡量这些结果,以便您可以决定更改是否成功。

2. Choose an area to focus on where your immaturity is especially painful. Value stream mapping will help you identify areas that need improvement. This book will help you understand what each improvement brings to the table and how to implement it. You should decide which improvements make sense for your organization, estimate their costs and benefits, and prioritize. You should define acceptance criteria to specify the results that you expect and how they will be measured, so that you can decide if the changes were successful.

3. 实施变更。首先,制定实施计划。从概念验证开始可能是有意义的。如果是这样,请选择您组织中真正受苦的部分——这些人将最有实施变革的动力,而正是在这里您将看到最显着的变化。

3. Implement the changes. First, create an implementation plan. It will probably make sense to begin with a proof of concept. If so, choose a part of your organization that is really suffering—these people will have the best motivation to implement change, and it is here that you will see the most dramatic change.

4. 进行更改后,使用您创建的验收标准来衡量更改是否达到了预期的效果。召开所有利益相关者和参与者的回顾会议,以了解变更的执行情况以及潜在的改进领域。

4. Once the changes have been made, use the acceptance criteria you created to measure if the changes had the desired effect. Hold a retrospective meeting of all stakeholders and participants to find out how well the changes were executed and where the potential areas for improvement are.

5. 重复这些步骤,以你的知识为基础。逐步推出改进,并将其推广到整个组织。

5. Repeat these steps, building upon your knowledge. Roll out improvements incrementally, and roll them out across your whole organization.

组织变革很难,详细的指南超出了本书的范围。我们可以提供的最重要的建议是逐步实施变革,并在进行时衡量影响。如果您尝试在整个组织中一步从第一级到第五级,您将失败。改变大型组织可能需要数年时间。寻找将带来的变化最有价值的东西和如何执行它们的方法应该被科学地对待:提出一个假设,然后进行测试。重复,并在过程中学习。不管你有多好,总有改进的可能。如果某事不起作用,请不要放弃该过程;尝试别的东西。

Organizational change is hard, and a detailed guide is beyond the scope of this book. The most important advice that we can offer is to implement change incrementally, and measure the impact as you go. If you try and go from level one to level five across your whole organization in one step, you will fail. Changing large organizations can take several years. Finding the changes that will deliver the most value and working out how to execute them should be treated scientifically: come up with a hypothesis, then test. Repeat, and learn in the process. No matter how good you are, it is always possible to improve. If something doesn’t work, don’t abandon the process; try something else.

项目生命周期

Project Lifecycle

每个软件开发项目都是不同的,但抽象出共同的元素并不难。特别是,我们可以有效地概括软件交付的生命周期。每个应用程序,就像每个团队一样,都有一个叙述弧线。谈论团队经历五个阶段已经变得很普遍:形成、风暴、规范、执行和哀悼/改革。同样,每个软件都会经历几个阶段。初始的高级图片可能包括以下阶段:识别、开始、启动、开发和部署以及操作。在继续更详细地检查构建和部署工程如何融入画面之前,我们将简要介绍这些阶段。

Every software development project is different, but it is not too hard to abstract common elements. In particular, we can usefully generalize the lifecycle of software delivery. Every application, like every team, has a narrative arc. It has become common to talk about teams passing through five phases: forming, storming, norming, performing, and mourning/reforming. In the same way, every piece of software goes through several phases. An initial high-level picture might include the following phases: identification, inception, initiation, development and deployment, and operation. We’ll briefly go through these phases before moving on to a more detailed examination of how build and deployment engineering fits into the picture.

鉴别

Identification

中型和大型组织将具有治理策略。企业将确定其战略目标,从而确定工作计划,使企业能够实现其战略目标。这些程序又分解为项目。

Medium-sized and large organizations will have a governance strategy. Businesses will determine their strategic objectives, leading to programs of work being identified which will enable the business to achieve its strategic objectives. These programs are in turn broken down into projects.

然而,根据我们的经验,在没有业务案例的情况下开始 IT 计划是非常普遍的。这可能会导致失败,因为如果没有商业案例,就不可能知道成功是什么样子的。你可能也是南方公园的内裤侏儒,他们的策略是

Nevertheless, in our experience it is startlingly common to begin an IT program without a business case. That will likely lead to failure, because it is impossible to know what success looks like without a business case. You might as well be the Underpants Gnomes in South Park, whose strategy is

1.收集内裤。

1. Collect underpants.

2. ?

2. ?

3. 利润。

3. Profit.

如果没有业务案例(这也适用于内部提供的服务),很难进行需求收集,并且不可能客观地对收集到的需求进行优先级排序。即使有了它,您也可以确定您最终得到的应用程序或服务将与您在最初收集需求时头脑中的解决方案有很大不同。

It is very hard to do requirements gathering, and impossible to objectively prioritize the requirements thus gathered, without a business case (this also applies to services that are provided internally). Even with it, you can be certain that the application or service you end up with will differ significantly from the solution you had in your head during the initial requirements gathering.

在开始收集需求之前需要准备的另一件重要事情是利益相关者列表,其中最重要的是业务发起人(在 PRINCE2 中称为高级负责人)。每个项目应该只有一个商业发起人,否则,任何规模合理的项目都不可避免地会在完成之前很久就因政治内斗而崩溃。这个业务负责人在 Scrum 中被称为产品负责人,在其他敏捷规程中被称为客户。然而,除了企业主之外,每个项目都需要一个由利益相关方组成的指导委员会——在公司中,这将包括其他高管和服务用户的代表;对于产品,它可能包括该产品的高知名度或其他代表性客户。IT 项目的其他内部利益相关者包括运营、销售、营销和支持人员,当然还有开发和测试团队。所有这些利益相关者都应该在项目的下一阶段出现:启动阶段。

The other essential thing to have in place before you start gathering requirements is a list of stakeholders, the most important of whom is the business sponsor (known in PRINCE2 as the senior responsible owner). There should only be one business sponsor for each project or, inevitably, any reasonably sized project will collapse from political infighting long before it is finished. This business owner is known in Scrum as the product owner, and in other agile disciplines as the customer. However, in addition to the business owner, every project needs a steering committee of interested parties—in a corporation, this will include other executives and representatives of the users of the service; for a product, it may include high-profile or otherwise representative customers of the product. Other internal stakeholders of an IT project include the operations, sales, marketing, and support personnel, and of course the development and testing teams. All these stakeholders should be represented during the next phase of the project: inception.

开端

Inception

这最简单地描述为编写任何产品代码之前的阶段。通常,在此期间收集和分析需求,并对项目进行松散的范围界定和计划。人们可能会认为这个阶段的价值很低,但即使是你的铁杆 agilista 作者也从痛苦的经历中了解到,这个阶段需要仔细规划和执行才能使软件项目取得成功。

This is most simply described as the phase before any production code is written. Typically, requirements are gathered and analyzed during this time, and the project is loosely scoped and planned. It can be tempting to dismiss this phase as being of low value, but even your hardcore agilista authors have learned from bitter experience that this phase needs to be carefully planned and executed for a software project to be successful.

从一开始就有许多可交付成果,其中一些会根据方法和项目类型而有所不同。但是,大多数启动应包括以下内容:

There are many deliverables from an inception, some of which will vary depending on methodology and the type of project. However, most inceptions should include the following:

• 商业案例,包括项目的估计价值。

• A business case, including the estimated value of the project.

• 高级功能性和非功能性需求列表(特别针对容量、可用性、服务连续性和安全性),其中的详细信息足以估计所涉及的工作并计划项目。

• A list of high-level functional and nonfunctional requirements (addressing in particular capacity, availability, service continuity, and security) with just enough detail to be able to estimate the work involved and plan the project.

• 发布计划,其中包括工作时间表和与项目相关的成本。为了获得此信息,通常要估计需求的相对大小、所需的编码工作量、与每个需求相关的风险以及人员配置计划。

• A release plan which includes a schedule of work and the cost associated with the project. In order to get this information, it is usual to estimate the relative size of the requirements, coding effort required, risk associated with each requirement, and a staffing plan.

• 测试策略。

• A testing strategy.

• 发布策略(稍后详细介绍)。

• A release strategy (more on this later).

• 架构评估,从而决定要使用的平台和框架。

• An architectural evaluation, leading to a decision on the platform and frameworks to use.

• 风险和问题日志。

• A risk and issue log.

• 开发生命周期的描述。

• A description of the development lifecycle.

• 执行此列表的计划说明。

• A description of the plan to execute this list.

这些可交付成果应包含足够的细节,以便项目可以开始工作,目标是最多在几个月内交付一些东西,如果可能的话,可以少得多。根据我们的经验,合理的最长项目期限约为三到六个月——最好选择下限。应根据项目的估计价值、估计成本和预测风险,在启动过程之后就项目是否应该继续进行进行/不进行决策。

These deliverables should contain enough detail that work can begin on the project, with the aim of having something delivered in a few months at most, and much less if possible. A reasonable maximum project horizon, in our experience, is about three to six months—with a preference for the lower limit. A go/no-go decision should be made following the inception process as to whether the project should go ahead, based on the estimated value of the project, estimated costs, and the predicted risks.

启动阶段最重要的部分——确保项目有成功机会的部分——是让所有利益相关者面对面地聚在一起。这意味着开发人员、客户、操作人员和管理人员。这些人之间的对话,导致对问题的共同理解要解决的问题和解决问题的方法,才是真正的可交付成果。上面的列表旨在构建对话结构,以便讨论重要问题、识别风险并制定应对策略。

The most important part of an inception—the bit that ensures that the project has a chance of success—is getting all the stakeholders together face-to-face. That means developers, customers, operations people, and management. The conversations between these people, leading to a shared understanding of the problem to be solved and the way to solve it, are the real deliverables. The list above is designed to structure the conversations so that the important issues are discussed, risks are identified, and strategies to deal with them are put in place.

这些可交付成果应该写下来,但由于它们是动态文档,我们希望每一个都会在整个项目中发生变化。为了以可靠的方式跟踪这些更改——以便每个人都可以轻松地看到当前图片是什么——您应该将这些文档提交到版本控制系统中。

These deliverables should be written down, but since they are living documents, we expect that each will change throughout the project. To keep track of these changes in a reliable way—so that everyone can easily see what the current picture is—you should commit these documents into a version control system.

一个警告:你在项目的这个阶段做出的每一个决定都是基于推测,并且会改变。根据您掌握的少量信息,您得出的结果是最佳猜测。在项目的这个阶段——你对它了解最少的阶段——花费太多精力是错误的。这些是必不可少的规划讨论和方向设置,但期望在您进行时改进和重新定义其中的许多内容。成功的项目成功地应对了变化。那些试图避免它的人往往会失败。在项目的这个阶段进行详细的规划、估算或设计是浪费时间和金钱。基础广泛的决策是现阶段唯一持久的决策。

One word of warning: Every decision you make at this stage of a project is based on speculation, and will change. What you produce is a best guess, based on the small amount of information you have. Expending too much effort at this stage of the project—the stage when you know the least that you will ever know about it—is a mistake. These are essential planning discussions and direction setting, but expect to refine and redefine many of them as you go. Successful projects cope with change successfully. Those that attempt to avoid it often fail. Detailed planning, estimation, or design at this stage of a project are wasted time and money. Broad-based decisions are the only kind of decisions durable at this stage.

引发

Initiation

启动后,您应该建立初始项目基础结构。这是启动阶段,通常会持续一到两周。以下列表描述了典型的启动阶段活动。

Following inception, you should establish initial project infrastructure. This is the initiation phase that will typically last one or two weeks. The following list describes typical initiation stage activities.

• 确保团队(分析师和经理,以及开发人员)拥有开始工作所需的硬件和软件

• Making sure that the team (analysts and managers, as well as developers) has the hardware and software that they need to begin work

• 确保基本的基础设施到位——例如互联网连接、白板、纸和笔、打印机、食物和饮料

• Making sure that basic infrastructure is in place—such as an Internet connection, a whiteboard, paper and pens, a printer, food, and drinks

• 创建电子邮件帐户并分配人员访问资源的权限

• Creating email accounts and assigning people permissions to access resources

• 设置版本控制

• Setting up version control

• 设置基本的持续集成环境

• Setting up a basic continuous integration environment

• 商定角色、责任、工作时间和会议时间(例如,站立会议、计划会议和展示)

• Agreeing upon roles, responsibilities, working hours, and meeting times (for example, stand-ups, planning meetings, and showcases)

• 准备第一周的工作并商定目标(而非截止日期)

• Preparing the work for the first week and agreeing on targets (not deadlines)

• 创建简单的测试环境和测试数据

• Creating a simple test environment and test data

• 稍微更详细地了解预期的系统设计:探索可能性确实是这个阶段的目标

• A slightly more detailed look at the intended system design: exploring the possibilities is really the aim at this stage

• 通过执行峰值(设计为概念验证的特定要求的一次性实施)识别和减轻任何分析、开发和测试风险

• Identify and mitigate any analysis, development, and testing risks by doing spikes (throwaway implementations of a particular requirement designed as a proof of concept)

• 开发故事或需求积压

• Developing the story or requirement backlog

• 设置项目结构并使用尽可能简单的故事,相当于“hello world”的架构,包括构建脚本和一些测试以进行持续集成

• Setting up the project structure and using the simplest possible story, the architectural equivalent of a “hello world,” including a build script and some tests to get continuous integration under way

分配足够的时间来轻松完成这些任务至关重要。如果没有人对正在开发的初始需求有验收标准,并且如果团队成员使用配置不佳的计算机、糟糕的工具和不稳定的 Internet 访问,那么尝试开始工作是没有效率和士气低落的。

It is vitally important to assign enough time to comfortably complete these tasks. It is unproductive and demoralizing to attempt to start work if nobody has acceptance criteria for the initial requirements being developed, and if team members are using poorly provisioned computers with bad tools and flaky Internet access.

虽然项目的这个阶段实际上是为了让基本的项目基础设施到位,而不应该被视为真正的开发迭代,但使用现实世界的问题来解决问题是非常有用的。在没有什么可测试的情况下构建测试环境,或者在没有什么可存储的情况下设置版本控制系统,都是一种乏味且低效的开始方式。选择你能找到的最简单的需求,尽管如此,解决一个实际问题并在设计方面建立一些初始方向。使用这个故事来确保您可以正确地对结果进行版本控制,可以在 CI 环境中运行测试,并且可以将结果部署到手动测试环境。目标是让这个故事完整且可证明,

While this stage in the project is really targeted at getting the basic project infrastructure in place, and should not be treated as a true development iteration, it is extremely useful to use a real-world problem to get things working. Building a test environment when there is nothing to test, or setting up a version control system when there is nothing to store, is a sterile and inefficient way to start. Pick the simplest possible requirement that you can find that is, nevertheless, solving a real problem and establishing some initial directions in terms of design. Use this story to make sure that you can version-control the results properly, that you can run your tests in your CI environment, and that you can deploy the results to a manual test environment. The target is to get this story complete and demonstrable, and establish all of the supporting infrastructure, by the end of the initiation phase.

完成后,您就可以开始实际开发了。

Once you’re done, you can get started on actual development.

开发和发布

Develop and Release

自然地,我们会推荐一个迭代和增量的过程来开发和发布软件。这可能不适用的唯一情况是当您从事涉及多方的大型国防项目时——但即使是航天飞机软件也是使用迭代过程实现的。1虽然很多人认同迭代过程的好处,但我们经常看到团队声称在进行迭代开发,但实际上并没有这样做。因此,有必要重申一下我们认为是迭代过程必不可少的基本条件。

Naturally, we would recommend an iterative and incremental process for developing and releasing software. The only time this might not be applicable is when you are working on a large defense project involving many parties—but even the space shuttle software was implemented using an iterative process.1 Although many people agree on the benefits of an iterative process, we have often seen teams that claim to be doing iterative development but actually aren’t. So it’s worth reiterating what we consider to be the essential, basic conditions for an iterative process.

• 您的软件始终在运行,正如自动测试套件所证明的那样,包括单元、组件和端到端验收测试,在您每次签入时运行。

• Your software is always working, as demonstrated by an automated test suite including unit, component, and end-to-end acceptance tests that run every time you check in.

• 在每次迭代中,您将可工作的软件部署到类似生产的环境中以向用户展示它(这是除了迭代之外还使过程增量的原因)。

• You deploy working software, at every iteration, into a production-like environment to showcase it to users (this is what makes the process incremental in addition to being iterative).

• 迭代不超过两周。

• Iterations are no longer than two weeks.

使用迭代过程有几个原因:

There are several reasons for using an iterative process:

• 如果您优先考虑具有高商业价值的功能,您可能会发现您的软件在项目结束之前很久就开始有用了。通常有充分的理由在新软件具有有用的功能时不发布它——但是没有比人们可以使用的工作系统更好的方法来将对项目最终成功的担忧转化为对新功能的兴奋。

• If you prioritize features with high business value, you may find that your software starts being useful long before the end of your project. There are often good reasons not to launch new software the moment that it has useful functionality—but there is no better way to turn worrying over the project’s eventual success into excitement over the new features than a working system that people can use.

• 您会定期从客户或发起人那里获得反馈,了解哪些有效以及哪些要求需要澄清或更改,这反过来意味着您正在做的事情更有可能有用。在项目开始时,没有人知道他们真正想要什么。

• You get regular feedback from your customer or sponsor on what works and what requirements need clarifying or changing, which in turn means that what you are doing is considerably more likely to be useful. Nobody knows what they really want at the beginning of a project.

• 事情只有在客户签字后才真正完成。定期展示发生这种情况的地方是跟踪进度的唯一远程可靠方法。

• Things are only really done when the customer signs them off. Having regular showcases where this happens is the only remotely reliable way to track progress.

• 让您的软件始终运行(因为您必须展示它)在您的团队中灌输纪律,以防止诸如长时间集成阶段、破坏一切的重构练习以及失去焦点和无处可去的实验等问题。

• Having your software working at all times (because you have to showcase it) instills discipline in your team that prevents problems such as long integration phases, refactoring exercises that break everything, and experiments that lose focus and go nowhere.

• 也许最重要的是,迭代方法强调在每次迭代结束时拥有生产就绪代码。这是软件项目中唯一真正有用的进度衡量标准,并且只有迭代方法才能提供。

• Perhaps most importantly, iterative methods place an emphasis on having production-ready code at the end of each iteration. This is the only really useful measure of progress in software projects, and one that only iterative methods provide.

一个经常被引用的原因进行迭代开发是指在完成大量功能之前,整个项目不会交付任何价值。虽然这个阈值对于许多项目来说可能是真实的,但我们上面列表中的最后一点特别适用于这种情况。在管理非迭代开发的大型项目时,所有进度衡量指标都是主观的,无法量化项目的实际进度。您在非迭代方法中看到的漂亮图表是基于对剩余时间的估计以及对后期集成、部署和测试的风险和成本的猜测。迭代开发基于开发团队生产用户同意适合目的的工作软件的速度,提供了客观的进展速度度量。只有生产就绪的工作代码,

An often-cited reason not to do iterative development is when the project as a whole won’t deliver any value until some huge quantity of features is complete. While this threshold may be real for many projects, the last point in our list above is especially applicable in this situation. When managing large projects that aren’t developed iteratively, all measures of progress are subjective, and there is no way to quantify the project’s actual progress. The nice charts you see in noniterative methods are based on estimations of time remaining and guesses at the risks and costs of later integration, deployment, and testing. Iterative development provides objective measures of the rate of progress based on the rate at which development teams produce working software that users agree is fit for purpose. Only working code that is production-ready, code that you can interact with, even if only in a UAT environment, provides any guarantee that any given feature is really finished.

至关重要的是,生产就绪还意味着该软件已经在具有生产规模数据集的类生产环境中测试了其非功能性需求。您关心的任何非功能特性,如容量、可用性、安全性等,都应该使用实际的负载和使用模式进行测试。这些测试应该是自动化的,并针对通过验收测试的软件的每个构建运行,以便您知道您的软件始终适合使用。我们将在第 9 章“测试非功能性需求”中对此进行更详细的介绍。

Crucially, production-readiness also means that the software has had its nonfunctional requirements tested on a production-like environment with a production-sized data set. Any nonfunctional characteristics you care about, such as capacity, availability, security, and so forth, should be tested using a realistic load and usage pattern. These tests should be automated and run against every build of the software that passes the acceptance tests so that you know your software is always fit for use. We cover this in more detail in Chapter 9, “Testing Nonfunctional Requirements.”

迭代开发过程的关键是优先化和并行化。对工作进行优先排序,以便分析师可以开始分析最有价值的功能,将工作提供给开发人员,然后再提供给测试人员,最后展示给真实用户或他们的代理人。使用来自精益制造的技术,这项工作可以并行进行,并且可以改变从事每项任务的人数以消除瓶颈。这导致了一个非常有效的开发过程。

The keys to an iterative development process are prioritization and parallelization. Work is prioritized so that analysts can begin analyzing the most valuable features, feed work to developers, and thence to testers and on to a showcase to real users or their proxies. Using techniques from lean manufacturing, this work can be parallelized and the number of people working on each task altered to remove bottlenecks. This leads to a very efficient development process.

迭代和增量开发有很多方法。最受欢迎的一种是 Scrum,一种敏捷的开发过程。我们已经看到 Scrum 在许多项目上取得成功,但我们也看到它失败了。以下是失败的三个最常见原因:

There are many approaches to iterative and incremental development. One of the most popular is Scrum, an agile development process. We have seen Scrum succeed on many projects, but we have also seen it fail. Here are the three most common reasons for failure:

缺乏承诺向 Scrum 的过渡可能是一个可怕的过程,尤其是对于项目领导而言。确保每个人定期开会讨论正在发生的事情,并建立定期回顾会议来分析绩效并寻求改进。敏捷流程依赖于透明度、协作、纪律和持续改进。实施敏捷过程时突然出现的大量有用信息可以将以前隐藏的不便真相推向聚光灯下。关键是要意识到这些问题一直都存在。既然您了解了它们,就可以修复它们。

Lack of commitment. The transition to Scrum can be a scary process, especially for project leadership. Make sure that everybody meets regularly to discuss what is going on, and establish regular retrospective meetings to analyze performance and seek improvements. Agile processes rely on transparency, collaboration, discipline, and continuous improvement. The sudden wealth of useful information that appears when agile processes are implemented can thrust inconvenient truths, previously hidden, into the spotlight. The key is to realize that these issues were there all along. Now that you know about them, you can fix them.

忽视好的工程Martin Fowler 等人描述了如果追随 Scrum 的人认为您可以忽略测试驱动开发、重构和持续集成等技术实践会发生什么 [99QFUz]。由初级开发人员破坏的代码库不会仅通过任何开发过程自动修复。

Ignoring good engineering. Martin Fowler, amongst others, has described what happens if people following Scrum think that you can ignore technical practices like test-driven development, refactoring, and continuous integration [99QFUz]. A codebase mangled by junior developers won’t be automatically fixed by any development process alone.

适应直到流程不再是敏捷流程人们通常将敏捷流程“改编”成他们认为在他们的特定组织中会更好地工作的东西。毕竟,敏捷流程是为满足个别项目的需求而设计的。然而,敏捷过程的元素通常以微妙的方式相互作用,很容易误解价值所在,尤其是对于没有这些迭代过程背景的人来说。我们怎么强调都不为过,首先假设所写的内容是正确的,然后首先按照所写的流程进行操作是多么重要。只有这样,一旦您了解了它是如何工作的,您就应该开始使它适应您的组织。

Adapting until the process is no longer an agile one. It is common for people to “adapt” agile processes into something they think will work better in their particular organization. Agile processes are designed to be tailored to meet the needs of individual projects, after all. However, the elements of agile processes often interact in subtle ways, and it is very easy to misunderstand where the value lies, particularly for people with no background in these iterative processes. We can’t emphasize enough how important it is to start by assuming that what is written is correct, and first follow the process as written. Only then, once you have seen how it works, should you start adapting it to your organization.

最后一点让诺基亚非常烦恼,以至于他们创建了一个测试来评估他们的团队是否真的在进行 Scrum。它分为两部分。

This last point was so troubling to Nokia that they created a test to evaluate whether their teams were really doing Scrum. It is divided into two parts.

你在做迭代开发吗?

Are you doing iterative development?

• 迭代的时间限制必须少于四个星期。2个

• Iterations must be time-boxed to less than four weeks.2

• 软件功能必须在每次迭代结束时进行测试和工作。

• Software features must be tested and working at the end of each iteration.

• 迭代必须在规范完成之前开始。

• The iteration must start before the specification is complete.

你在做 Scrum 吗?

Are you doing Scrum?

• 你知道产品负责人是谁吗?

• Do you know who the product owner is?

• 产品待办事项是否按业务价值排列优先级?

• Is the product backlog prioritized by business value?

• 产品积压是否有团队创建的估计?

• Does the product backlog have estimates created by the team?

• 是否有项目经理(或其他人)干扰团队的工作?

• Are there project managers (or others) disrupting the work of the team?

为了澄清最后一点,我们认为项目经理可以通过管理风险、消除资源缺乏等障碍以及促进高效交付来发挥有益作用。但也有一些项目经理不做这些事情。

To clarify the last point, we believe project managers can play a useful role by managing risks, removing roadblocks such as a lack of resources, and facilitating efficient delivery. But there are some project managers who do none of these things.

手术

Operation

通常,第一个版本不是最后一个。接下来发生的事情在很大程度上取决于项目。开发和发布阶段可能会继续全力以赴,或者团队可能会缩减规模。如果该项目是试点项目,则可能会发生相反的情况,团队可能会成长。

Typically, the first release is not the last. What happens next very much depends on the project. The development and release phase may continue at full tilt, or the team might be reduced in size. If the project is a pilot, the opposite may happen and the team may grow.

真正的迭代和敏捷过程的一个有趣方面是,在许多方面,项目的运营阶段不一定与常规开发阶段有任何不同。正如我们所说,大多数项目不会停留在首次发布时,而是会继续开发新功能。一些项目会有一系列的维护版本,可能会修复不可预见的问题,可能会调整项目以满足新发现的用户需求,可能作为滚动开发计划的一部分。在所有这些情况下,新功能将被识别、优先排序、分析、开发、测试和发布。这与项目的常规开发阶段没有什么不同。在这方面,让这些阶段一起崩溃是消除风险的最佳方法之一,

An interesting aspect of a genuinely iterative and agile process is that in many ways, the operational phase of a project is not necessarily any different from the regular development phase. Most projects, as we said, don’t stop at the point of first release, and will continue to develop new functionality. Some projects will have a series of maintenance releases, perhaps fixing unforeseen problems, perhaps tailoring the project to meet newly discovered user needs, perhaps as part of a rolling program of development. In all these cases, new features will be identified, prioritized, analyzed, developed, tested, and released. This is no different from the regular development phase of the project. In this respect, making these phases collapse together is one of the best ways to eliminate risk, and is at the core of continuous delivery as described in the rest of this book.

正如我们在本节前面提到的,将发布时间拉到对任何给定系统有意义的尽可能早的时间点是非常有用的。您将获得的最佳反馈来自真实用户;这里的关键是尽快发布您的软件以供实际使用。然后,您可以尽快对有关软件可用性和实用性的任何问题或反馈做出反应。尽管如此,在系统发布供一般使用之前和之后的项目阶段之间仍存在一些差异需要考虑。变更管理,尤其是与应用程序及其公共接口生成的数据有关的变更管理,一旦首次公开发布就成为一个重要问题(参见第 12 章“管理数据”)。

As we mentioned earlier in this section, it is very useful to pull the time of release to the earliest possible point that makes sense for any given system. The best feedback you will get is that from real users; the key here is to release your software for real use as soon as you can. Then you can react to any problems or feedback about the usability and utility of your software as quickly as possible. Despite this, there are some differences to consider between the phases of the project before and after the system has been released for general use. Change management, particularly that concerned with data generated by the application and its public interfaces, becomes a significant issue once the first public release has occurred (see Chapter 12, “Managing Data”).

风险管理流程

A Risk Management Process

风险管理是确保:

Risk management is the process of making sure that:

• 主要项目风险已经确定。

• The main project risks have been identified.

• 已制定适当的缓解策略来管理它们。

• Appropriate mitigating strategies have been put in place to manage them.

• 在整个项目过程中继续识别和管理风险。

• Risks continue to be identified and managed throughout the course of the project.

风险管理流程应具有几个关键特征:

There are several key characteristics that a risk management process should have:

• 项目团队报告状态的标准结构

• A standard structure for project teams to report status

• 项目团队按照标准定期更新他们的进度

• Regular updates, following the standard, from the project team on their progress

• 一个仪表板,项目经理可以在其中跟踪所有项目的当前状态和趋势

• A dashboard where program managers can track current status and trends across all projects

• 由项目外人员定期审计以确保风险得到有效管理

• Regular audits by someone outside the project to ensure that risks are being managed effectively

风险管理 101

Risk Management 101

重要的是要注意,并非所有风险都需要制定缓解策略。有些事件是灾难性的,一旦发生,将无法采取任何措施来减轻它们。一颗巨大的小行星摧毁了地球上的所有生命是一个极端的例子,但你接受我们的观点。现实生活中通常存在特定于项目的风险会导致项目被取消,例如立法或经济变化、组织管理结构的变化或关键项目发起人的撤职。规划一个成本太高或太耗时而不值得实施的缓解策略毫无意义——例如,用于小公司的时间和费用应用程序的多站点多节点备份系统。

It is important to note that not all risks need to have a mitigating strategy put in place. Some events are so catastrophic that, should they occur, nothing could be done to mitigate them. A huge asteroid destroying all life on the planet is an extreme example, but you take our point. There are often real-life project-specific risks that would lead to the project being cancelled, such as legislative or economic changes, changes to the management structure of an organization, or the removal of a key project sponsor. There is little point planning a mitigation strategy that would be too costly or time-consuming to be worth putting in place—for example, a multisite multinode backup system for a small company’s time and expenses application.

一种常见的风险管理模型(参见Tom DeMarco 和 Timothy Lister 的《与熊共舞》 )根据风险的影响对所有风险进行分类——如果它们成为现实,它们将造成多大的损害——以及它们的可能性——它们发生的可能性有多大。这些被结合起来评估每个风险的严重性。最容易从财务角度考虑影响:如果风险成为现实,将会损失多少钱?然后可以将可能性建模为 0(不可能)和 1(确定)之间的概率。严重性就是影响乘以概率,这可以让您根据金额估算风险的严重性。这使您在决定采用何种策略来减轻风险时可以进行非常简单的计算:缓解策略的成本是否高于风险的严重性?如果是这样,它可能不值得实施。

A common model of risk management (See Dancing with Bears by Tom DeMarco and Timothy Lister) categorizes all risks in terms of their impact—how much damage they would cause if they materialize—and their likelihood—how likely they are to occur. These are combined to assess each risk’s severity. It is easiest to consider the impact in financial terms: how much money would be lost if the risk materializes? Then the likelihood can be modeled as a probability between 0 (impossible) and 1 (certain). Severity is then the impact multiplied by the probability, which gives you an estimate of the severity of the risk in terms of an amount of money. This allows you to make a very simple calculation when deciding what strategies to put in place to mitigate the risk: does the mitigation strategy cost more than the severity of the risk? If so, it’s probably not worth implementing.

风险管理时间表

Risk Management Timeline

就我们在​​本章前面介绍的项目生命周期模型而言,风险管理过程应该在启动阶段结束时开始,在启动阶段结束时重新审视,然后在整个开发和部署阶段定期重新审视。

In terms of the project lifecycle model that we presented earlier in this chapter, the risk management process should begin at the end of the inception phase, be revisited at the end of the initiation phase, and then regularly revisited throughout the development and deployment phase.

成立结束

End of Inception

在这个阶段应该准备好两个重要的可交付成果。第一个是作为初始部分创建的发布策略。您应该验证我们在创建发布策略部分中讨论的所有注意事项都已考虑在内。如果没有,团队计划如何管理相关风险?

There are two important deliverables that should be ready at this stage. The first is the release strategy that has been created as part of inception. You should verify that all the considerations we discuss in the section on creating a release strategy have been taken into account. If they haven’t, how is the team planning to manage the relevant risks?

第二个可交付成果是启动阶段的计划。有时在启动阶段和启动阶段之间存在间隔,在这种情况下,该计划可以延迟到启动开始前几天。否则,它需要作为启动结束的一部分发生。

The second deliverable is a plan for the initiation phase. Sometimes there is a gap between the inception and initiation phases, in which case this plan can be delayed until a few days before the start of initiation. Otherwise, it needs to happen as part of the end of initiation.

启蒙结束

End of Initiation

这里的关键是确保团队准备好开始开发软件。他们应该已经有一个正在运行的持续集成服务器来编译代码(如果适用)并运行一个自动化测试套件。他们应该有一个他们正在部署到的类似于生产的环境。应该制定测试策略,规定如何通过作为部署管道的一部分运行的自动化测试套件来测试应用程序的功能和非功能(特别是容量)需求。

The key here is to make sure that the team is ready to start developing software. They should already have a continuous integration server running which compiles the code (if applicable) and runs an automated test suite. They should have a production-like environment that they are deploying to. A testing strategy should be in place that lays out how the functional and nonfunctional (in particular capacity) requirements of the application will be tested through an automated test suite run as part of the deployment pipeline.

开发和发布风险缓解

Develop and Release Risk Mitigation

即使有最好的准备,开发和部署阶段也有很多可能会出现严重错误,有时甚至比您更快认为可能。我们都经历过或听过有关项目在部署日期之前不交付代码,或者系统部署但由于容量问题而立即失败的恐怖故事。在这个阶段,你需要问自己的问题是,“什么地方可能出错?” 因为如果你不问自己这个问题,当事情真的出错时你就不会准备好任何答案。

Even with the best preparation, there are many ways in which a development and deployment phase can go horribly wrong, sometimes more quickly than you thought possible. We have all experienced or heard horror stories about projects that deliver no code until past the deployment date, or systems that deployed but failed instantly because of capacity problems. Throughout this phase, the question that you need to ask yourself is, “What can possibly go wrong?” because if you don’t ask yourself the question, you won’t have any answers ready when things do go wrong.

在许多方面,风险管理的真正价值在于它为发展建立了一个环境,从而为发展活动带来了一种深思熟虑的、具有风险意识的方法。作为一个团队,考虑可能出错的行为可能会成为可能被遗漏的具体需求的来源,但它也让我们能够对风险给予足够的关注,以便在它成为问题之前避免它。如果您认为第三方供应商可能会延误他们的截止日期,您将提前监控他们的进度,从而有时间在截止日期到来之前计划和适应延误。

In many ways the real value of risk management is that it establishes a context for development, and so engenders a thoughtful, risk-aware approach to development activities. The act of considering, as a team, what may go wrong can be a source of concrete requirements that may otherwise have been missed, but it also allows us to pay enough attention to a risk to avert it before it becomes an issue. If you think that a third-party supplier may slip their deadline, you will monitor their progress ahead of time, and thus have time to plan for and accommodate the slip before the deadline arrives.

在此阶段,您的目标是识别、跟踪和管理您能想到的任何可管理风险。有几种识别风险的方法:

In this phase you are aiming to identify, track, and manage any manageable risks that you can think of. There are several ways of identifying risks:

• 查看部署计划。

• Look at the deployment plan.

• 在每次展示后定期进行项目小型回顾,并让团队在这次会议期间就风险进行头脑风暴。

• Have regular project miniretrospectives after every showcase and get the team to brainstorm risks during this meeting.

• 将风险识别作为每日站立会议的一部分。

• Make risk identification part of your daily stand up meeting.

有几种常见的构建相关和部署相关风险需要注意——我们将在下一节中介绍这些风险。

There are several common build-related and deployment-related risks to look out for—we’ll cover these in the next section.

如何进行风险管理练习

How to Do a Risk-Management Exercise

重要的是不要打扰定期按时交付工作软件且几乎没有缺陷的团队。然而,重要的是要快速发现是否有一个项目从外部看起来做得很好,但实际上会失败。幸运的是,迭代方法的一大好处是发现是否是这种情况相对简单。如果您正在进行迭代开发,您应该在每次迭代结束时从类似生产的环境中展示工作软件。这可能是实实在在进步的最好证明。您的团队生成实际工作代码的速度,足以让真实用户使用,并将其部署到类似生产的主机环境中——速度——不会说谎,即使估计是这样。

It’s important not to disturb a team that is regularly delivering working software on schedule with few defects. However, it is important to discover quickly if there is a project that appears to be doing fine from the outside but is actually going to fail. Fortunately, one of the great benefits of iterative methods is that it is relatively simple to discover if this is the case. If you are doing iterative development, you should be showcasing working software at the end of every iteration from a production-like environment. This is possibly the best demonstration of tangible progress. The rate at which your team produces real working code, good enough for real users to use, and deploys it into a production-like host environment—velocity—doesn’t lie, even if estimates do.

将此与非迭代方法进行比较——或者就此而言,与迭代时间过长的迭代方法进行比较。在此类项目中,有必要深入了解团队工作的细节,深入研究各种项目文件和跟踪系统,以了解还有多少工作需要完成以及已经完成了多少工作。完成此分析后,有必要根据现实验证你的结果,这是一个极其困难和不可靠的过程,任何尝试过的人都可以验证。

Compare this to noniterative methods—or, for that matter, iterative methods where the iterations are too long. In such projects it is necessary to go into the details of the working of the team, dive into the various project documents and tracking systems to find out how much work is left to be done and how much work has been done. Once this analysis has been done, it becomes necessary to validate your results against reality, which is an extremely hard and unreliable process, as anybody who has tried to do it can verify.

分析任何项目的一个很好的起点是提出这些问题(这个列表对我们的几个项目很有效):

A good starting point to analyze any project is to pose these questions (this list has worked well for us on several projects):

• 您如何跟踪进度?

• How are you tracking progress?

• 您如何预防缺陷?

• How are you preventing defects?

• 您如何发现缺陷?

• How are you discovering defects?

• 您如何跟踪缺陷?

• How are you tracking defects?

• 你怎么知道故事讲完了?

• How do you know a story is finished?

• 您如何管理您的环境?

• How are you managing your environments?

• 您如何管理配置,例如测试用例、部署脚本、环境和应用程序配置、数据库脚本和外部库?

• How are you managing configuration, such as test cases, deployment scripts, environment and application configuration, database scripts, and external libraries?

• 您多久展示一次工作特点?

• How often do you showcase working features?

• 您多久做一次回顾?

• How often do you do retrospectives?

• 您多久运行一次自动化测试?

• How often do you run your automated tests?

• 您如何部署软件?

• How are you deploying your software?

• 您如何构建软件?

• How are you building your software?

• 您如何确保您的发布计划可行并为运营团队所接受?

• How are you ensuring that your release plan is workable and acceptable to the operations team?

• 您如何确保您的风险和问题日志是最新的?

• How are you ensuring that your risk-and-issue log is up-to-date?

这些问题不是规定性的,这很重要,因为每个团队都需要有一定的灵活性来选择最适合他们特定需求的流程。相反,它们是开放式的,确保您可以获得尽可能多的有关项目背景和方法的信息。但是,他们关注的是结果,因此您可以验证团队是否真的能够交付,并且您将能够发现任何警告信号。

These questions are not prescriptive, which is important because every team needs to have a certain amount of flexibility to choose the most suitable process for their specific needs. Instead, they are open-ended, ensuring that you can get as much information as possible on the project’s context and approach. However, they focus on the outcome, so you can validate that the team will actually be able to deliver, and you will be able to spot any warning signs.

常见的分娩问题——症状和原因

Common Delivery Problems—Their Symptoms and Causes

在本节中,我们将描述在构建、部署、测试和发布软件过程中出现的一些常见问题。尽管您的项目几乎任何事情都可能出错,但有些事情比其他事情更容易出错。通常很难弄清楚您的项目到底出了什么问题——您所拥有的只是症状。当事情确实出错时,锻炼如何及早发现,并确保监测这些症状。

In this section we describe a few common problems that arise during the process of building, deploying, testing, and releasing software. Although almost anything could go wrong with your project, some things are more likely to go wrong than others. It is usually quite hard to work out what is actually going wrong with your project—all you have is symptoms. When things do go wrong, work out how that could have been spotted early, and ensure that these symptoms are monitored.

观察到症状后,您需要找出根本原因。任何给定的症状都可能是许多潜在原因的表现。为此,我们使用了一种称为“根本原因分析”的技术。这是一个非常简单的过程的奇特名称。面对一系列症状时,只需表现得像个小孩子,反复问团队“为什么?” 建议您问“为什么?” 至少五次。虽然这个过程听起来很荒谬,但我们发现它非常有用,而且完全安全。

Once you have observed the symptoms, you need to discover the root cause. Any given symptom can be a manifestation of a number of possible underlying causes. To do this, we use a technique called “root cause analysis.” This is a fancy name for a very simple procedure. When confronted with a set of symptoms, simply behave like a small child and repeatedly ask the team, “Why?” It is recommended that you ask “Why?” at least five times. Although this process sounds almost absurd, we have found it to be incredibly useful and totally foolproof.

一旦知道了根本原因,就必须真正解决它。然而,这超出了保证的范围。因此,事不宜迟,这里列出了常见症状,按其根本原因分组。

Once you know the root cause, you have to actually fix it. However this is beyond the remit of assurance. So, without further ado, here’s a list of common symptoms, grouped by their root cause.

不频繁或错误的部署

Infrequent or Buggy Deployments

问题

Problem

部署构建需要很长时间,而且部署过程很脆弱。

It takes a long time to deploy the build, and the deployment process is brittle.

症状

Symptoms

• 测试人员需要很长时间才能关闭错误。请注意,此症状可能不完全是由不频繁部署引起的,但它是一个可能的根本原因。

• It takes a long time for bugs to be closed by testers. Note that this symptom may not be exclusively caused by infrequent deployments, but it is one possible root cause.

• 客户测试或签署故事需要很长时间。

• It takes a long time for stories to be tested or signed off by the customer.

• 测试人员正在寻找开发人员很久以前修复的错误。

• Testers are finding bugs that developers fixed a long time ago.

• 没有人信任 UAT、性能或 CI 环境,而且人们对发布何时可用持怀疑态度。

• Nobody trusts the UAT, performance, or CI environments, and people are skeptical as to when a release will be available.

• 展示很少发生。

• Showcases rarely happen.

• 很少能证明应用程序正在运行。

• The application can rarely be demonstrated to be working.

• 团队的速度(进度)比预期慢。

• The team’s velocity (rate of progress) is slower than expected.

可能的原因

Possible causes

可能的原因有很多。以下是一些最常见的原因:

There are many possible reasons. Here are a few of the commonest causes:

• 部署过程不是自动化的。

• The deployment process is not automated.

• 没有足够的可用硬件。

• There is not enough hardware available.

• 硬件和操作系统配置管理不当。

• The hardware and operating system’s configuration are not managed correctly.

• 部署过程取决于团队无法控制的系统。

• The deployment process depends on systems outside the team’s control.

• 没有足够的人了解构建和部署过程。

• Not enough people understand the build and deployment process.

• 测试人员、开发人员、分析人员和操作人员在开发过程中没有充分协作。

• Testers, developers, analysts, and operations personnel are not collaborating sufficiently during development.

• 开发人员没有遵守通过进行小的增量更改来保持应用程序正常运行的纪律,因此经常破坏现有功能。

• Developers are not being disciplined about keeping the application working by making small, incremental changes, and so frequently break existing functionality.

申请质量差

Poor Application Quality

问题

Problem

交付团队未能实施有效的测试策略。

Delivery teams are failing to implement an effective testing strategy.

症状

Symptoms

• 回归错误不断出现。

• Regression bugs keep popping up.

• 即使您的团队花费了大部分时间来修复它们,缺陷的数量仍然在增加(当然,这种症状只有在您拥有有效的测试过程时才会表现出来)。

• The number of defects keeps increasing even when your team spends most of its time fixing them (of course this symptom will only be manifested if you have an effective testing process).

• 客户抱怨产品质量差。

• Customers complain of a poor-quality product.

• 每当收到新功能请求时,开发人员都会发出呻吟声,看起来很惊恐。

• Developers groan and look horrified whenever a new feature request arrives.

• 开发人员抱怨代码的可维护性,但没有任何改进。

• Developers complain about the maintainability of the code, but nothing ever gets better.

• 实施新功能需要越来越多的时间,团队开始落后。

• It takes an ever-increasing amount of time to implement new functionality, and the team starts falling behind.

可能的原因

Possible causes

这个问题基本上有两个根源:测试人员与交付团队其他成员之间的协作效率低下,以及自动化测试实施不当或不充分。

There are essentially two sources of this problem: ineffective collaboration between testers and the rest of the delivery team, and poorly implemented or inadequate automated tests.

• 测试人员在功能开发期间不与开发人员协作。

• Testers do not collaborate with developers during development of features.

• 故事或功能被标记为“完成”,但没有编写全面的自动化测试,没有经过测试人员签字,或者没有从类似生产的环境中向用户展示。

• Stories or features are marked as “done” without comprehensive automated tests written, without being signed off by testers, or without being showcased to users from a production-like environment.

• 缺陷通常被输入到待办事项清单中,而没有通过自动测试来检测回归问题而当场修复。

• Defects are routinely entered into a backlog without being fixed on the spot with an automated test to detect regression problems.

• 开发人员或测试人员没有足够的开发自动化测试套件的经验。

• The developers or testers don’t have sufficient experience developing automated test suites.

• 团队不了解为他们正在使用的技术或平台编写的最有效的测试类型。

• The team does not understand the most effective types of tests to write for the technology or platform that they are working on.

• 开发人员在没有足够的测试覆盖率的情况下工作,可能是因为他们的项目管理不允许他们有时间实施自动化测试。

• The developers are working without sufficient test coverage, perhaps because their project management doesn’t allow them time to implement automated testing.

• 该系统是一个将被丢弃的原型(尽管我们遇到过一些最初作为原型开发但从未被丢弃的重要生产系统)。

• The system is a prototype that will be discarded (though we have come across a few important production systems that were originally developed as prototypes but were never discarded).

请注意,当然,可以通过自动化测试超越顶层——我们知道一个项目,整个团队花了数周时间只编写测试。当客户发现没有可用的软件时,团队被解雇了。然而,这个警示故事应该结合上下文来理解:到目前为止,最常见的失败模式是自动化测试太少,而不是太多。

Please note that it is, of course, possible to go over the top with automated tests—we know of one project where the entire team spent several weeks writing nothing but tests. When the customer discovered that there was no working software, the team was fired. However, this cautionary tale should be taken in context: The most common failure mode, by far, is that there is too little automated testing, not too much.

管理不善的持续集成过程

Poorly Managed Continuous Integration Process

问题

Problem

构建过程没有得到妥善管理。

The build process is not properly managed.

症状

Symptoms

• 开发人员签入的频率不够(每天至少一次)。

• Developers don’t check in often enough (at least once a day).

• 提交阶段永久中断。

• The commit stage is permanently broken.

• 存在大量缺陷。

• There is a high number of defects.

• 每次发布之前都有一个很长的集成阶段。

• There is a long integration phase before each release.

可能的原因

Possible causes

• 自动化测试运行时间过长。

• The automated tests take too long to run.

• 提交阶段运行时间太长(少于五分钟是理想的,超过十分钟是不可接受的)。

• The commit stage takes too long to run (less than five minutes is ideal, more than ten minutes is unacceptable).

• 自动测试间歇性地失败,给出误报。

• The automated tests fail intermittently, giving false positives.

• 任何人都无权恢复签到。

• Nobody is empowered to revert check-ins.

• 没有足够的人了解并能够更改 CI 流程。

• Not enough people understand, and can make changes to, the CI process.

配置管理不善

Poor Configuration Management

问题

Problem

无法委托环境,也无法使用自动化过程可靠地安装应用程序。

Environments can’t be commissioned, and applications installed reliably, using an automated process.

症状

Symptoms

• 生产环境中的神秘故障。

• Mysterious failures in production environments.

• 新部署是紧张、可怕的事件。

• New deployments are tense, scary events.

• 大型团队致力于环境配置和管理。

• Large teams are dedicated to environment configuration and management.

• 生产部署通常需要回滚或打补丁。

• Deployments to production often have to be rolled back or patched.

• 生产环境的停机时间不可接受。

• Unacceptable downtime of production environment.

可能的原因

Possible causes

• UAT 和生产环境不同。

• UAT and production environments are different.

• 用于对生产和暂存环境进行更改的更改管理流程不佳或执行不力。

• A poor or badly enforced change management process for making changes to production and staging environments.

• 运营、数据管理团队和交付团队之间的协作不足。

• Insufficient collaboration between operations, data management teams, and delivery teams.

• 无法有效监控生产和暂存环境以检测事件。

• Ineffective monitoring of production and staging environments to detect incidents.

• 应用程序中内置的检测和日志记录不足。

• Insufficient instrumentation and logging built into applications.

• 对应用程序的非功能性需求的测试不足。

• Insufficient testing of the nonfunctional requirements of applications.

合规与审计

Compliance and Auditing

许多大公司必须遵守管理其行业的具有法律约束力的法规。例如,所有在美国注册的上市公司都必须遵守 2002 年的萨班斯-奥克斯利法案(通常缩写为 Sarbox 或 SOX)。美国医疗保健公司必须遵守HIPAA 的规定。处理信用卡信息的系统必须符合 PCI DSS 标准。几乎每个领域都以这样或那样的方式受到监管,而且 IT 系统在设计时经常必须考虑到一些规定。

Many large companies are required to comply with legally binding regulations that govern their industry. For example, all US registered public companies are required to comply with the Sarbanes-Oxley Act of 2002 (often abbreviated to Sarbox or SOX). US health care companies have to comply with the provisions of HIPAA. Systems that deal with credit card information must conform to the PCI DSS standard. Pretty much every field is regulated in one way or another, and IT systems frequently have to be designed with some regulations in mind.

我们既没有空间也没有意志力来检查涵盖每个国家/地区每个行业的法规,这些法规在任何情况下都经常变化。但是,我们想花一些时间来讨论总体监管,特别是在对软件发布过程进行严格控制的环境中。许多此类监管制度都需要审计跟踪,以便能够识别生产环境中的每一次变化,它来自哪些代码行,谁接触过它们,以及谁批准了流程中的步骤。此类法规在从金融到医疗保健的许多行业中都很常见。

We have neither the space nor the willpower to examine the regulations covering every industry in every country, which in any case change frequently. However, we would like to spend some time discussing regulation in general, specifically in environments that define close controls on the software release process. Many such regulatory regimes require audit trails that make it possible to identify, for every change in a production environment, what were the lines of code that it came from, who touched them, and who approved the steps in the process. Such regulations are common in many industries from finance to health care.

以下是我们看到的用于执行此类法规的一些常见策略:

Here are some common strategies we have seen employed for enforcing these kinds of regulations:

• 锁定能够访问“特权”环境的人员。

• Locking down who is able to access “privileged” environments.

• 创建和维护有效且高效的变更管理流程,以对特权环境进行变更。

• Creating and maintaining an effective and efficient change management process for making changes to privileged environments.

• 在执行部署之前需要管理层的批准。

• Requiring approvals from management before deployments can be performed.

• 要求记录从构建到发布的每个过程。

• Requiring every process, from building to release, to be documented.

• 创建授权壁垒以确保创建软件的人员无法将其部署到生产环境中,以防止潜在的恶意干预。

• Creating authorization barriers to ensure that the people who create the software are not able to deploy it into production environments, as a protection against potential malicious interventions.

• 要求对每个部署进行审计,以准确了解所做的更改。

• Requiring every deployment to be audited to see exactly what changes are being made.

像这样的策略对于受监管的组织来说是必不可少的,并且可以导致停机时间和缺陷数量的大幅减少。尽管如此,它们的名声并不好,因为实施起来太容易了,但改变起来却更加困难。但是,部署管道可以相当轻松地实施这些策略,同时实现高效的交付过程。在本节中,我们提出了一些原则和做法,以确保遵守此类监管制度,同时保持较短的周期时间。

Strategies like these are essential in organizations subject to regulation, and can lead to drastic reductions in downtime and defect counts. Nonetheless they have a bad reputation because it is all too easy to implement them in ways that make change more difficult. However, the deployment pipeline makes it possible to enforce these strategies fairly easily while enabling an efficient delivery process. In this section, we present some principles and practices to ensure compliance with such regulatory regimes while maintaining short cycle times.

文档自动化

Automation over Documentation

许多公司坚持文件是审计的核心。我们不敢苟同。一张纸上写着你以某种方式做了某事并不能保证你确实做了那件事。咨询界充斥着人们通过(例如)ISO 9001 审核的故事,他们提供了一堆“证明”他们已经实施审核的文件,并指导他们的员工如何在检查员提问时给出正确答案。

Many companies insist that documentation is central to auditing. We beg to differ. A piece of paper that says you did something in a certain way is no guarantee that you actually did that thing. The world of consultancy abounds in tales of people passing (for example) ISO 9001 audits by supplying a bunch of documents which “proved” they had implemented them and coaching their staff on how to give the correct answers when questioned by inspectors.

文档也有过时的坏习惯。文档越详细,过时的速度就越快。当它这样做时,人们通常不会费心去更新它。每个人都至少听过一次以下对话:

Documentation also has a nasty habit of going out of date. The more detailed a document is, the more quickly it is likely to go out of date. When it does so, people don’t usually bother to update it. Everybody has heard the following conversation at least once:

接线员:“我遵循了您上个月通过电子邮件发送给我的部署过程,但它不起作用。”

Operator: “I followed the deployment process you emailed me last month, but it doesn’t work.”

开发人员:“哦,我们改变了部署的工作方式。您需要将这组新文件复制过来并设置权限x。” 或者更糟糕的是,“这很奇怪,让我看看……”,然后花几个小时弄清楚发生了什么变化以及如何部署它。

Developer: “Oh, we changed the way deployment works. You need to copy this new set of files over and set permission x.” Or worse, “That’s strange, let me take a look... ” followed by hours of working out what has changed and how to get it deployed.

自动化解决了所有这些问题。自动化脚本是必须运行的流程的文档。通过强制使用它们,您可以确保它们是最新的,并且该过程已按照您的意图精确执行。

Automation solves all of these problems. Automated scripts are the documentation of your processes that must work. By enforcing their use, you ensure both that they are up-to-date and that the process has been performed precisely as you intend.

加强可追溯性

Enforcing Traceability

通常需要能够跟踪更改历史,从生产中的内容到生成它的源代码控制版本。我们要强调有两种有助于此过程的实践。

It is often necessary to be able to trace the history of changes, from what is in production to the source control versions that produced it. There are two practices that help with this process that we want to emphasize.

• 只创建一次二进制文件,并将相同的二进制文件部署到您在构建过程的第一阶段创建的生产环境中。您可以通过对二进制文件进行散列(例如使用 MD5 或 SHA1)并将它们存储在安全数据库中来确保二进制文件相同。许多工具会自动为您完成这项工作。

• Only create binaries once, and deploy the same binaries into production that you created in the first stage of your build process. You can ensure that the binaries are the same by taking a hash of them (using MD5 or SHA1, for example), and storing them in a secure database. Many tools will do this for you automatically.

• 使用完全自动化的流程让您的二进制文件完成部署、测试和发布流程,记录谁在何时做了什么。同样,市场上有几种工具可以帮助解决这个问题。

• Use a fully automated process to take your binaries through the deployment, test, and release process which records who did what when. Again, there are several tools on the market that can help with this.

即使采取了这些预防措施,仍有可能引入未经授权的更改:当二进制文件首次从源代码创建时。所需要的只是有人获得对完成此操作的框的访问权限,并在编译或汇编过程中将文件插入文件系统,以实现此目的。解决这个问题的一种方法是一步创建二进制文件,使用在访问受控的盒子上执行的自动化过程。在这种情况下,必须能够自动配置和管理此环境,以便可以调试创建过程中的任何问题。

Even with these precautions, there is a window when unauthorized changes can be introduced: when the binaries are first created from source code. All it takes is somebody gaining access to the box where this is done and inserting files into the filesystem during the compile or assembly process for this to happen. One way to solve this problem is to create binaries in a single step, using an automated process which executes on a box which is access-controlled. In this case it is essential to be able to provision and manage this environment automatically so that it is possible to debug any problems with the creation process.

在孤岛中工作

Working in Silos

通常情况下,大型组织有不同的部门来执行不同的职能。许多组织都有独立的开发、测试、运营、配置管理、数据管理和架构团队。在本书的大部分内容中,我们提倡在团队之间和团队内部进行开放和自由的交流与协作,因此在组织中负责软件创建和发布不同方面的部分之间设置障碍存在一些危险。但是,有些职责显然应该属于一组而不是另一组。在受监管的环境中,许多重要活动都需要接受审计员和安全团队的审查,他们的工作是确保组织不会面临法律风险或任何类型的安全漏洞。

It is often the case that large organizations have separate departments for different functions. Many organizations have independent teams for development, testing, operations, configuration management, data management, and architecture. In much of this book we have promoted open and free communication and collaboration between and within teams, so there are some dangers to creating barriers between the parts of your organization responsible for different aspects of software creation and release. However, there are some responsibilities that should clearly belong in one group and not another. In regulated environments, many important activities are subject to review by auditors and security teams, whose job it is to ensure that the organization is not exposed to legal risks or security breaches of any kind.

这种责任分离,在正确的时间点以正确的方式管理,不一定是坏事。理论上,每个为组织工作的人都会将组织的最大利益放在心上,这意味着他们会与其他部门有效合作。然而,通常情况并非如此。几乎无一例外,这种协作的缺乏是由于团队之间沟通不畅造成的。我们坚信,最有效的团队在跨职能小组中开发软件,这些小组由来自定义、开发、测试和发布软件所需的所有不同学科的人员组成。这些团体应该坐在一起——否则,他们就无法从彼此的知识中获益。

Such separation of responsibilities, at the right point and managed in the right way, need not be a bad thing. In theory, everybody who works for an organization will keep the best interests of that organization at heart, which means that they will cooperate effectively with other departments. However, this is often not the case. Almost without exception, such a lack of collaboration results from poor communication between groups. We believe very strongly that the most effective teams develop software in cross-functional groups that are composed of people from all of the different disciplines required to define, develop, test, and release software. These groups should sit together—when they don’t, they don’t benefit from each other’s knowledge.

一些监管制度使得这种跨职能团队难以建立。如果您在一个更加孤立的组织中,本书中描述的流程和技术——特别是实施部署管道——有助于防止这些孤岛使交付过程效率低下。然而,最重要的解决方案是从项目开始就在孤岛之间进行通信。这应该采取几种形式:

Some regulatory regimes make such cross-functional teams difficult to establish. If you are in a more siloed organization, the processes and techniques described throughout this book—in particular, implementing a deployment pipeline—help to prevent these silos from making the delivery process inefficient. However, the most important solution is communication between silos from the beginning of a project. This should take several forms:

• 参与项目交付的每个人,包括来自每个筒仓的人,都应该在每个项目开始时开会。我们将这群人称为发布工作组,因为他们的工作是保持发布过程的正常进行。他们的任务应该是制定项目的发布策略,详见第 10 章“部署和发布应用程序”。

• Everybody involved in the delivery of a project, including somebody from each of the silos, should meet at the beginning of every project. We’ll call this group of people the release working group, because their job is to keep the release process working. Their task should be to put together a release strategy for the project, as detailed in Chapter 10, “Deploying and Releasing Applications.”

• 发布工作组应在整个项目期间定期开会。他们应该对自上次见面以来的项目进行回顾,计划如何改进并执行计划。使用戴明循环:计划、执行、检查、行动。

• The release working group should meet regularly throughout the project. They should run a retrospective on the project since the last time they met, plan how to improve things, and execute the plan. Use the Deming cycle: plan, do, check, act.

• 即使还没有用户,软件也应该尽可能频繁地发布——这意味着至少每次迭代——到类似生产的环境。一些团队实行持续部署,这意味着发布通过管道中所有阶段的每个更改。这是原则的应用:“如果它痛,就更频繁地做它。” 我们怎么强调这种做法的重要性都不为过。

• Even if it has no users yet, the software should be released as often as possible—this means at least every iteration—to a production-like environment. Some teams practice continuous deployment, which means releasing every change that passes all the stages in your pipeline. This is an application of the principle: “If it hurts, do it more frequently.” We can’t stress enough how important this practice is.

• 项目状态,包括我们在第 431 页的“风险管理过程”部分中提到的仪表板,应该对参与构建、部署、测试和发布过程的每个人都可用,最好是在每个人都可以看到的大监视器上。

• Project status, including the dashboard we mentioned in the “A Risk Management Process” section on page 431, should be available to everyone involved in the build, deploy, test, and release process, preferably on big monitors that everybody can see.

更换管理层

Change Management

在受监管的环境中,构建、部署、测试和发布过程的某些部分通常需要获得批准。特别是,手动测试环境、暂存和生产应始终处于严格的访问控制之下,以便只能通过组织的变更管理流程对它们进行更改。这似乎是不必要的官僚主义,但事实上研究表明,这样做的组织具有更短的平均故障间隔时间 (MTBF) 和平均修复时间 (MTTR)(请参阅可视化操作手册,第 13 页)。

In regulated environments, it is often essential for parts of the build, deploy, test, and release process to require approval. In particular, manual testing environments, staging, and production should always be under strict access control so that changes to them can only be made through the organization’s change management process. This may seem unnecessarily bureaucratic, but in fact research has demonstrated that organizations which do this have lower mean time between failures (MTBF) and mean time to repair (MTTR) (see The Visible Ops Handbook, p. 13).

如果您的组织由于测试和生产环境的不受控制的更改而无法满足其服务水平,我们建议采用以下流程来管理批准:

If your organization has a problem meeting its service levels due to uncontrolled changes to testing and production environments, we suggest the following process for managing approvals:

• 创建一个变更咨询委员会,由来自您的开发团队、运营团队、安全团队、变更管理团队和业务的代表组成。

• Create a Change Advisory Board with representatives from your development team, operations team, security team, change management team, and the business.

• 决定哪些环境属于变更管理流程的范围。确保这些环境是受访问控制的,以便只能通过此过程进行更改。

• Decide which environments fall under the purview of the change management process. Ensure that these environments are access-controlled so that changes can only be made through this process.

• 建立可用于提出变更请求和管理批准的自动化变更请求管理系统。任何人都应该能够看到每个变更请求的状态以及谁批准了它。

• Establish an automated change request management system that can be used to raise a change request and manage approvals. Anyone should be able to see the status of each change request and who has approved it.

• 任何时候任何人想要对环境进行更改,无论是部署新版本的应用程序、创建新的虚拟环境,或进行配置更改,必须通过更改请求来完成。

• Any time anybody wants to make a change to an environment, whether deploying a new version of an application, creating a new virtual environment, or making a configuration change, it must be done through a change request.

• 需要针对每个更改的补救策略,例如退出的能力。

• Require a remediation strategy, such as the ability to back out, for every change.

• 有变革成功的验收标准。理想情况下,创建一个现在失败但一旦更改成功就会通过的自动化测试。在您的运营管理仪表板上放置一个指示器,其中包含测试状态(请参阅第 323 页的“行为驱动的监控”部分)。

• Have acceptance criteria for the success of a change. Ideally, create an automated test that now fails but will pass once the change is successful. Put an indicator on your operations management dashboard with the status of the test (see the “Behavior-Driven Monitoring” section on page 323).

• 有一个应用更改的自动化流程,以便每当更改被批准时,都可以通过按下按钮(或单击链接或其他任何方式)来执行。

• Have an automated process for applying changes, so that whenever the change is approved, it can be performed by pressing a button (or clicking a link, or whatever).

最后一部分听起来很难,但我们希望现在它也听起来很熟悉,因为它一直是本书的主要焦点。将经过审核和授权的更改部署到生产环境的机制与将相同更改部署到任何其他环境的机制相同,只是增加了授权:向部署管道添加访问控制是一项微不足道的练习。它是如此简单,以至于进一步扩展审计和授权通常是有意义的:所有更改都由拥有环境的人批准。这意味着您可以使用为测试环境创建的相同自动化来更改属于变更管理流程的环境。这也意味着您已经测试了您创建的自动化流程。

The last part sounds difficult, but we hope that by now it also sounds familiar, since it has been the primary focus of this book. The mechanism for deploying a change that is audited and authorized to a production environment is the same as deploying the same change to any other environment, with the addition of the authorization: Adding access control to a deployment pipeline is a trivial exercise. It is so simple that it often makes sense to extend the auditing and authorization further: All changes are approved by whoever owns the environment. This means that you can use the same automation you created for your testing environments to make changes to environments that fall under the change management process. It also means you have already tested the automated processes you created.

CAB 如何决定是否应该执行变更?这只是风险管理的问题。进行更改的风险是什么?有什么好处?如果风险大于收益,则不应进行更改,或者应进行其他风险较小的更改。CAB 还应该能够对故障单进行评论、请求更多信息或提出修改建议。所有这些流程都应该能够通过自动票务系统进行管理。

How does the CAB decide whether a change should be executed? This is simply a matter of risk management. What is the risk of making the change? What is the benefit? If the risks outweigh the benefits, the change should not be made, or another less risky change should be made. The CAB should also be able to make comments on tickets, request more information, or suggest modifications. All these processes should be able to be managed through the automated ticketing system.

最后,在实施和管理变更批准流程时还应遵循三个原则:

Finally, there are three more principles that should be followed when implementing and managing a change approval process:

• 在系统上保留指标并使它们可见。更改需要多长时间才能获得批准?有多少更改正在等待批准?拒绝更改的比例是多少?

• Keep metrics on the system and make them visible. How long does it take for a change to be approved? How many changes are waiting for approval? What proportion of changes are denied?

• 保留可验证系统成功并使其可见的指标。什么是 MTBF 和 MTTR?变更的周期是多少?ITIL 文献中定义了更完整的指标列表。

• Keep metrics that validate the success of the system and make them visible. What’s the MTBF and MTTR? What is the cycle time for a change? There is a more complete list of metrics defined in the ITIL literature.

• 定期对系统进行回顾,邀请组织中每个部门的代表,并根据这些回顾的反馈改进系统。

• Hold regular retrospectives on the system, inviting representatives from each of your organization’s units, and work to improve the system based on feedback from these retrospectives.

概括

Summary

管理对于每个项目的成功都至关重要。良好的管理创建了能够有效交付软件的流程,同时确保适当地管理风险并遵守监管​​制度。然而,太多的组织——出于最好的意图——建立了不符合这些目标的糟糕的管理结构。本章旨在描述一种处理一致性和性能的管理方法。

Management is vital to the success of every project. Good management creates processes enabling efficient delivery of software, while ensuring that risks are managed appropriately and regulatory regimes are complied with. Nevertheless, too many organizations—with the best of intentions—create poor management structures that meet none of these goals. This chapter is intended to describe an approach to management that deals with both conformance and performance.

我们的构建和发布成熟度模型旨在提高组织绩效。它可以让您确定您的交付实践的有效性,并提出改进方法。此处描述的风险管理流程以及我们的常见反模式列表旨在帮助您创建一种策略,以便在问题发生时立即发现问题,以便您可以在问题容易修复时及早纠正它们。我们在本章(和本书)中花了很大一部分时间讨论迭代的、增量的过程;这是因为迭代、增量交付是有效风险管理的关键。没有迭代的、增量的过程,您就没有客观的方法来衡量项目的进度或应用程序的适用性。

Our build and release maturity model is targeted at improving organizational performance. It allows you to identify how effective your delivery practices are, and suggests ways to improve them. The risk management process described here, along with our list of common antipatterns, is designed to help you create a strategy to identify problems as soon as they occur, so you can rectify them early when they are easy to fix. We have spent a good proportion of this chapter (and this book) discussing iterative, incremental processes; this is because iterative, incremental delivery is the key to effective risk management. Without an iterative, incremental process, you have no objective way to gauge your project’s progress or your application’s fitness for purpose.

最后,我们希望我们已经证明,迭代交付与构建、部署、测试和发布部署管道中包含的软件的自动化过程相结合,不仅与一致性和性能目标兼容,而且是最有效的实现这些目标的方式。此过程使参与交付软件的人员之间能够加强协作,提供快速反馈,以便可以快速发现错误和不必要或实施不当的功能,并为减少重要指标(周期时间)铺平道路。反过来,这意味着可以更快地交付有价值的高质量软件,从而以更低的风险带来更高的盈利能力。这样就实现了良好治理的目标。

Finally, we hope that we have demonstrated that iterative delivery, combined with an automated process for building, deploying, testing, and releasing software embodied in the deployment pipeline, is not only compatible with the goals of conformance and performance, but is the most effective way of achieving these goals. This process enables greater collaboration between those involved in delivering software, provides fast feedback so that bugs and unnecessary or poorly implemented features can be discovered quickly, and paves the route to reducing that vital metric, cycle time. This, in turn, means faster delivery of valuable, high-quality software, which leads to higher profitability with lower risk. Thus the goals of good governance are achieved.

参考书目

Bibliography

1. Adzic、Gojko,弥合沟通鸿沟:通过示例和敏捷验收测试进行规范,Neuri,2009 年。

1. Adzic, Gojko, Bridging the Communication Gap: Specification by Example and Agile Acceptance Testing, Neuri, 2009.

2. Allspaw, John,容量规划的艺术:扩展 Web 资源,O'Reilly,2008 年。

2. Allspaw, John, The Art of Capacity Planning: Scaling Web Resources, O’Reilly, 2008.

3. Allspaw, John, Web Operations: Keeping the Web on Time,O'Reilly,2010 年。

3. Allspaw, John, Web Operations: Keeping the Web on Time, O’Reilly, 2010.

4. Ambler、Scott 和 Pramodkumar Sadalage,重构数据库:进化数据库设计,Addison-Wesley,2006 年。

4. Ambler, Scott, and Pramodkumar Sadalage, Refactoring Databases: Evolutionary Database Design, Addison-Wesley, 2006.

5. Beck、Kent 和 Cynthia Andres,极限编程解释:拥抱变化(第 2 版),Addison-Wesley,2004 年。

5. Beck, Kent, and Cynthia Andres, Extreme Programming Explained: Embrace Change (2nd edition), Addison-Wesley, 2004.

6. Behr、Kevin、Gene Kim 和 George Spafford,可见操作手册:通过 4 个实际和可审计的步骤实施 ITIL,IT 流程研究所,2004 年。

6. Behr, Kevin, Gene Kim, and George Spafford, The Visible Ops Handbook: Implementing ITIL in 4 Practical and Auditable Steps, IT Process Institute, 2004.

7. 史蒂文·布兰克 (Blank, Steven),顿悟的四个步骤:产品获胜的成功策略,CafePress,2006 年。

7. Blank, Steven, The Four Steps to the Epiphany: Successful Strategies for Products That Win, CafePress, 2006.

8. Bowman, Ronald,数据中心和系统的业务连续性规划:战略实施指南,Wiley,2008 年。

8. Bowman, Ronald, Business Continuity Planning for Data Centers and Systems: A Strategic Implementation Guide, Wiley, 2008.

9. Chelimsky, Mark, The RSpec Book: Behavior Driven Development with RSpec, Cucumber, and Friends , The Pragmatic Programmers, 2010.

9. Chelimsky, Mark, The RSpec Book: Behaviour Driven Development with RSpec, Cucumber, and Friends, The Pragmatic Programmers, 2010.

10. Clark, Mike,实用项目自动化:如何构建、部署和监控 Java 应用程序,实用程序员,2004 年。

10. Clark, Mike, Pragmatic Project Automation: How to Build, Deploy, and Monitor Java Applications, The Pragmatic Programmers, 2004.

11. Cohn, Mike,敏捷成功:使用 Scrum 进行软件开发,Addison-Wesley,2009 年。

11. Cohn, Mike, Succeeding with Agile: Software Development Using Scrum, Addison-Wesley, 2009.

12. Crispin、Lisa 和 Janet Gregory,敏捷测试:测试人员和敏捷团队实用指南,Addison-Wesley,2009 年。

12. Crispin, Lisa, and Janet Gregory, Agile Testing: A Practical Guide for Testers and Agile Teams, Addison-Wesley, 2009.

13. DeMarco、Tom 和 Timothy Lister,与熊共舞华尔兹:管理软件项目的风险,Dorset House,2003 年。

13. DeMarco, Tom, and Timothy Lister, Waltzing with Bears: Managing Risk on Software Projects, Dorset House, 2003.

14. Duvall、Paul、Steve Matyas 和 Andrew Glover,持续集成:提高软件质量和降低风险,Addison-Wesley,2007 年。

14. Duvall, Paul, Steve Matyas, and Andrew Glover, Continuous Integration: Improving Software Quality and Reducing Risk, Addison-Wesley, 2007.

15. 埃里克埃文斯,领域驱动设计,Addison-Wesley,2003 年。

15. Evans, Eric, Domain-Driven Design, Addison-Wesley, 2003.

16. Feathers, Michael, Working Effectively with Legacy Code , Prentice Hall, 2004.

16. Feathers, Michael, Working Effectively with Legacy Code, Prentice Hall, 2004.

17. Fowler, Martin,企业应用程序架构模式,Addison-Wesley,2002 年。

17. Fowler, Martin, Patterns of Enterprise Application Architecture, Addison-Wesley, 2002.

18. Freeman、Steve 和 Nat Pryce,Growing Object-Oriented Software, Guided by Tests,Addison-Wesley,2009 年。

18. Freeman, Steve, and Nat Pryce, Growing Object-Oriented Software, Guided by Tests, Addison-Wesley, 2009.

19. Gregory, Peter,傻瓜 IT 灾难恢复计划,傻瓜书,2007 年。

19. Gregory, Peter, IT Disaster Recovery Planning for Dummies, For Dummies, 2007.

20. Kazman、Rick 和 Mark Klein,基于属性的架构风格,卡内基梅隆软件工程学院,1999 年。

20. Kazman, Rick, and Mark Klein, Attribute-Based Architectural Styles, Carnegie Mellon Software Engineering Institute, 1999.

21. Kazman、Rick、Mark Klein 和 Paul Clements,ATAM:体系结构评估方法,卡内基梅隆软件工程研究所,2000 年。

21. Kazman, Rick, Mark Klein, and Paul Clements, ATAM: Method for Architecture Evaluation, Carnegie Mellon Software Engineering Institute, 2000.

22. Meszaros,Gerard,xUnit 测试模式:重构测试代码,Addison-Wesley,2007 年。

22. Meszaros, Gerard, xUnit Test Patterns: Refactoring Test Code, Addison-Wesley, 2007.

23. Nygard, Michael, Release It!: Design and Deploy Production-Ready Software , The Pragmatic Programmers, 2007.

23. Nygard, Michael, Release It!: Design and Deploy Production-Ready Software, The Pragmatic Programmers, 2007.

24. Poppendieck、Mary 和 Tom Poppendieck,实施精益软件开发:从概念到现金,Addison-Wesley,2006 年。

24. Poppendieck, Mary, and Tom Poppendieck, Implementing Lean Software Development: From Concept to Cash, Addison-Wesley, 2006.

25. Poppendieck、Mary 和 Tom Poppendieck,精益软件开发:敏捷工具包,Addison-Wesley,2003 年。

25. Poppendieck, Mary, and Tom Poppendieck, Lean Software Development: An Agile Toolkit, Addison-Wesley, 2003.

26. Sadalage,Pramod,持续数据库集成的秘诀, Pearson Education,2007 年。

26. Sadalage, Pramod, Recipes for Continuous Database Integration, Pearson Education, 2007.

27. Sonatype 公司,Maven:权威指南,O'Reilly,2008 年。

27. Sonatype Company, Maven: The Definitive Guide, O’Reilly, 2008.

28. ThoughtWorks, Inc., ThoughtWorks Anthology: Essays on Software Technology and Innovation , The Pragmatic Programmers, 2008.

28. ThoughtWorks, Inc., The ThoughtWorks Anthology: Essays on Software Technology and Innovation, The Pragmatic Programmers, 2008.

29. Wingerd、Laura 和 Christopher Seiwald,“软件配置管理的高级最佳实践”,论文于 1999 年 7 月在比利时布鲁塞尔举行的第八届国际软件配置管理研讨会上阅读。

29. Wingerd, Laura, and Christopher Seiwald, “High-Level Best Practices in Software Configuration Management,” paper read at Eighth International Workshop on Software Configuration Management, Brussels, Belgium, July 1999.

指数

Index

一种

A

A/B 测试,264

A/B testing, 264

土豚, 218

Aardvarks, 218

构建脚本中的绝对路径,164

Absolute paths in build scripts, 164

抽象层

Abstraction layer

用于验收测试,198–204

for acceptance tests, 198–204

用于数据库访问,335

for database access, 335

用于针对 UI、88、201进行测试

for testing against UI, 88, 201

在抽象分支中,349

in branch by abstraction, 349

验收标准

Acceptance criteria

和非功能性需求,227–228

and nonfunctional requirements, 227–228

和测试数据,336

and test data, 336

作为可执行规范,195–198

as executable specifications, 195–198

用于验收测试, 85 , 89

for acceptance tests, 85, 89

用于自动化测试,93

for automated tests, 93

变革管理,441

for change management, 441

对于组织变革,420

for organizational change, 420

管理, 197

managing, 197

往返,200

round-tripping, 200

验收测试阶段

Acceptance test stage

和测试数据,339–341

and test data, 339–341

作为部署管道的一部分,110

as part of deployment pipeline, 110

工作流程,187

workflow of, 187

验收测试

Acceptance tests

反对用户界面,88

against UI, 88

和分析,190

and analysis, 190

和异步200,207–210

and asynchronicity, 200, 207–210

和云计算220–222,313

and cloud computing, 220–222, 313

和外部系统,210

and external systems, 210

和团队规模,214

and team size, 214

和测试双打,210–212

and test doubles, 210–212

和交付过程,99-101

and the delivery process, 99–101

和部署管道,213-218

and the deployment pipeline, 213–218

和超时,207–210

and timeouts, 207–210

和虚拟化,310

and virtualization, 310

应用程序驱动层,198-204

application driver layer, 198–204

作为...的一部分:

as part of:

CI, 61

CI, 61

提交阶段,120

commit stage, 120

集成管道,362

integration pipeline, 362

自动化, 86–88 , 136

automating, 86–88, 136

后门,206

back doors in, 206

85的定义

definition of, 85

122–126的部署管道门

deployment pipeline gate of, 122–126

封装,206–207

encapsulating, 206–207

失败,124

failing, 124

脆度为88 , 125 , 200 , 205

fragility of, 88, 125, 200, 205

功能性, 124

functional, 124

隔离, 205 , 220

isolation in, 205, 220

分层, 191

layering, 191

可维护性,190–192

maintainability of, 190–192

手册, 86 , 189

manual, 86, 189

并行执行, 199 , 220 , 336

parallel executing, 199, 220, 336

性能,218-222

performance of, 218–222

记录和回放191,197

record-and-playback for, 191, 197

200 219可靠性

reliability of, 200, 219

在开发机器上运行62,190

running on development machines, 62, 190

136、213–214屏幕录制

screen recording for, 136, 213–214

共享资源,219-220

shared resources for, 219–220

测试数据管理336,339–341

test data managing in, 336, 339–341

针对 UI 进行测试,192–193

testing against UI, 192–193

变成容量测试,238

turning into capacity tests, 238

UI耦合, 125 , 192 , 201

UI coupling, 125, 192, 201

用例,86

use cases for, 86

验证, 192

validating, 192

价值主张188–193,351

value proposition for, 188–193, 351

与单元测试,188

vs. unit tests, 188

谁拥有它们, 125 , 215

who owns them, 125, 215

201–204的窗口驱动程序模式

window driver pattern of, 201–204

访问控制, 284 , 438–439

Access control, 284, 438–439

基础设施,285–286

for infrastructure, 285–286

AccuRev, 385 , 399 , 403

AccuRev, 385, 399, 403

活动目录,290

ActiveDirectory, 290

ActiveRecord 迁移,328

ActiveRecord migrations, 328

演员模特,359

Actor model, 359

调整敏捷流程,427

Adapting agile processes, 427

自适应测试336,338

Adaptive tests, 336, 338

敏捷开发,427

Agile development, 427

频繁发布,131

frequent releases in, 131

重构,330

refactorings in, 330

展示期间,90

showcases during, 90

AgileDox,201

AgileDox, 201

长鳍金枪鱼,151

Albacore, 151

警报,281–282

Alerts, 281–282

算法和应用性能,230

Algorithms and application performance, 230

备用路径,86

Alternate path, 86

亚马逊, 316

Amazon, 316

亚马逊EC2、221、261、312 _ _ _ _

Amazon EC2, 221, 261, 312

亚马逊网络服务 (AWS )261,312–315

Amazon Web Services (AWS), 261, 312–315

分析,193–195

Analysis, 193–195

和验收测试,190

and acceptance tests, 190

和增量开发,349

and incremental development, 349

和非功能性需求,226–228

and nonfunctional requirements, 226–228

分析师,193

Analysts, 193

蚂蚁,147–148

Ant, 147–148

AntHill Pro, 58 , 126 , 255 , 373

AntHill Pro, 58, 126, 255, 373

反模式

Antipatterns

开发后部署,7-9

deploying after development, 7–9

手动部署软件,5–7

deploying software manually, 5–7

长寿的分支机构,411

long-lived branches, 411

手动配置管理,9-10

manual configuration management, 9–10

非功能性需求,230

of nonfunctional requirements, 230

由部署管道解决,105

solved by the deployment pipeline, 105

阿帕奇,320

Apache, 320

API(应用程序编程接口340、357、367、369

API (Application Programming Interface), 340, 357, 367, 369

应用配置

Application configuration

和测试,46

and testing, 46

管理, 39

management of, 39

应用程序驱动程序,191

Application driver, 191

应用程序驱动程序模式,198–204

Application driver pattern, 198–204

应用生命周期

Application lifecycle

和发布策略,250

and the release strategy, 250

阶段,421-429

phases of, 421–429

应用服务器,296

Application servers, 296

审批流程, 112 , 250 , 254 , 267 , 285 , 437

Approval process, 112, 250, 254, 267, 285, 437

APT 存储库,294

APT repository, 294

资质,294

Aptitude, 294

拱门,396

Arch, 396

建筑学

Architecture

和组件,346

and components, 346

和康威定律,360

and Conway’s Law, 360

和非功能性需求105、226–228

and nonfunctional requirements, 105, 226–228

作为开始的一部分,423

as part of inception, 423

归档

Archiving

作为操作的要求,282

as a requirement of operations, 282

作为发布策略的一部分,251

as part of the release strategy, 251

工件存储库

Artifact repository

和部署,256

and deployment, 256

和流水线依赖,366

and pipelining dependencies, 366

和部署管道175-177,374-375

and the deployment pipeline, 175–177, 374–375

审计,373

auditing, 373

在共享文件系统中实现,375

implementing in a shared filesystem, 375

管理,373–375

managing, 373–375

特定于组织,355

organization-specific, 355

清洗, 175

purging, 175

与版本控制,166

vs. version control, 166

Artifactory, 111 , 355 , 361 , 373 , 375

Artifactory, 111, 355, 361, 373, 375

文物, 111

Artifacts, 111

组件

Assemblies

和依赖管理,353

and dependency management, 353

和标签,374

and labels, 374

和可追溯性,166

and traceability, 166

异步

Asynchrony

和验收测试, 200 , 207–210

and acceptance testing, 200, 207–210

和容量测试,239

and capacity testing, 239

和单元测试,180

and unit testing, 180

ATAM(架构权衡分析方法),227

ATAM (Architectural Tradeoff Analysis Method), 227

原子提交,383–384

Atomic commits, 383–384

原子测试205,337

Atomic tests, 205, 337

审计

Auditing

和验收标准,198

and acceptance criteria, 198

和数据归档,282

and data archiving, 282

和部署,273

and deployment, 273

和分布式版本控制,396

and distributed version control, 396

和环境管理,129

and environment management, 129

并锁定基础设施,286

and locking down infrastructure, 286

和糟糕的工具,300

and poor tools, 300

和重建二进制文件,114

and rebuilding binaries, 114

和部署管道,418

and the deployment pipeline, 418

作为非功能性需求,227

as a nonfunctional requirement, 227

作为 IT 运营的要求,280–281

as a requirement of IT operations, 280–281

作为...的一部分:

as part of:

送货,429

delivery, 429

发布策略,251

release strategy, 251

436–441的管理

management of, 436–441

工件存储库,373

of artifact repositories, 373

基础设施变化,287

of infrastructure changes, 287

手动流程,6

of manual processes, 6

自动化测试

Automated tests

和持续部署,266

and continuous deployment, 266

和运行时配置,348

and runtime configuration, 348

和基于流的版本控制,403

and stream-based version control, 403

作为项目启动的一部分,430

as part of project initiation, 430

作为先决条件:

as prerequisite for:

CI, 59–60

CI, 59–60

合并, 390

merging, 390

质量,434

quality, 434

失败,注释掉,70

failing, commenting out, 70

用于基础设施,323

for infrastructure, 323

另请参阅 验收测试容量测试单元测试

See also Acceptance tests, Capacity tests, Unit tests

自动化

Automation

作为持续交付的原则,25

as a principle of continuous delivery, 25

的好处,5-7

benefits of, 5–7

对反馈的影响,14

effect on feedback, 14

为了降低风险,418

for risk reducing, 418

的重要性,12

importance of, 12

数据库初始化,326–327

of database initialization, 326–327

数据库迁移327–331,340

of database migration, 327–331, 340

部署,152–153

of deployment, 152–153

文档287,437–438

vs. documentation, 287, 437–438

自主基础设施, 278 , 292 , 301

Autonomic infrastructure, 278, 292, 301

可用性, 91 , 314 , 423

Availability, 91, 314, 423

蔚蓝, 313 , 317

Azure, 313, 317

B

验收测试中的后门,206

Back doors in acceptance tests, 206

打了退堂鼓

Backing out

规划, 129 , 251 , 441

planning, 129, 251, 441

方式,131-132

ways of, 131–132

积压

Backlogs

缺陷,99–101

defect, 99–101

要求,425

requirement, 425

作为...的一部分:

as part of:

发布计划,251

release plan, 251

服务连续性规划,282

service continuity planning, 282

网络,302

network, 302

向后兼容性,371

Backwards compatibility, 371

泥球, 351 , 359

Ball of mud, 351, 359

基线

Baseline

和版本控制,166

and version control, 166

和虚拟化,305

and virtualization, 305

环境, 51 , 155

environments, 51, 155

狂欢,282

Bash, 282

批处理,167

Batch processing, 167

集市, 396

Bazaar, 396

BCFG2,291 _

Bcfg2, 291

行为驱动开发195,204,323

Behavior-driven development, 195, 204, 323

行为驱动的监控,322–323

Behavior-driven monitoring, 322–323

板凳,243

Bench, 243

Beta 测试,90

Beta testing, 90

大的,可见的显示。查看 仪表板

Big, visible displays. See Dashboards

大表,315

BigTable, 315

二进制文件

Binaries

和包装,154

and packaging, 154

和悲观锁定,387

and pessimistic locking, 387

和版本控制35,373

and version control, 35, 373

建筑,438

building, 438

只有一次,113–115

only once, 113–115

134的定义

definition of, 134

特定环境,115

environment-specific, 115

在简历中,383

in CVS, 383

管理,373–375

managing, 373–375

版本控制创造性33、175、354、363、373 _ _

re-creatability from version control, 33, 175, 354, 363, 373

从50中分离出配置

separating out configuration from, 50

共享文件系统,166

shared filesystem for, 166

二进制文件格式,300

Binary file formats, 300

比特桶,394

BitBucket, 394

比特守护者, 386 , 395

BitKeeper, 386, 395

商务对话,311

BizTalk, 311

刀片逻辑161、287、289、291、296 _ _ _ _ _ _

BladeLogic, 161, 287, 289, 291, 296

蓝绿部署261–262、301、332–333 _ _

Blue-green deployments, 261–262, 301, 332–333

BMC, 156 , 161 , 289 , 291 , 318

BMC, 156, 161, 289, 291, 318

自举问题,372

Bootstrapping problem, 372

瓶颈, 106 , 138

Bottlenecks, 106, 138

边界值分析,86

Boundary value analysis, 86

抽象分支, 334–335 , 349–351 , 360 , 415

Branch by abstraction, 334–335, 349–351, 360, 415

分支机构

Branches

整合, 389

integrating, 389

维护, 389

maintenance, 389

释放, 389

release, 389

分枝

Branching

和CI, 59 , 390–393

and CI, 59, 390–393

按特征分支,36 , 81 , 349 , 405 , 410–412

branch by feature, 36, 81, 349, 405, 410–412

按团队划分,412–415

branch by team, 412–415

发布分支346,367

branch for release, 346, 367

延期,390

deferred, 390

388–393的定义

definition of, 388–393

早期,390

early, 390

环保, 388

environmental, 388

功能性, 388

functional, 388

在简历中,383

in CVS, 383

在颠覆中,384

in Subversion, 384

组织, 388

organizational, 388

物理, 388

physical, 388

389的政策

policies of, 389

程序, 388

procedural, 388

原因, 381

reasons of, 381

脆性试验, 125 , 191

Brittle tests, 125, 191

BSD(伯克利软件发行版),355

BSD (Berkeley Software Distribution), 355

BSD 端口,294

BSD ports, 294

错误队列。 积压缺陷

Bug queue. See Backlogs, defect

建造

Build

和组件,360

and components, 360

和测试目标,166–167

and test targets, 166–167

自动化作为 CI 的先决条件,57

automating as prerequisite for CI, 57

破碎的:

broken:

并签到,66

and checking in, 66

68-69岁时回家

going home when, 68–69

修理责任, 70–71 , 174

responsibility for fixing, 70–71, 174

恢复, 69

reverting, 69

连续的,65

continuous, 65

慢速测试失败,73

failing for slow tests, 73

优化, 361

optimizing, 361

推广, 108

promoting, 108

调度, 65 , 118–119 , 127

scheduling, 65, 118–119, 127

工具,145

tools for, 145

触发,369–370

triggering, 369–370

构建网111,185

Build grid, 111, 185

搭建梯子,372

Build ladder, 372

建筑灯,63

Build lights, 63

建筑大师,174

Build master, 174

建立管道,110

Build pipeline, 110

在26–27 , 83建立质量

Build quality in, 26–27, 83

BuildForge,58 岁

BuildForge, 58

建设者,151

Buildr, 151

舱壁模式,98

Bulkhead pattern, 98

业务分析师。 分析师

Business analysts. See Analysts

商业案例,422

Business case, 422

业务治理。 治理

Business governance. See Governance

商业智能,317

Business intelligence, 317

商业赞助商,422

Business sponsor, 422

商业价值

Business value

和分析,193

and analysis, 193

和非功能性需求,226

and nonfunctional requirements, 226

通过验收测试保护,189

protecting by acceptance tests, 189

C

C

C/C++

C/C++

使用 Make 和 SCons 构建,147

building with Make and SCons, 147

编译, 146

compiling, 146

C#,282

C#, 282

加利福尼亚州,318

CA, 318

CAB(变更咨询委员会280,440

CAB (Change Advisory Board), 280, 440

金丝雀发布, 235 , 262–265

Canary releasing, 235, 262–265

和持续部署,267

and continuous deployment, 267

和数据库迁移,333

and database migration, 333

容量

Capacity

和云计算,314

and cloud computing, 314

作为项目失败的原因,431

as a cause of project failure, 431

225的定义

definition of, 225

设计,230

designing for, 230

测量,232–234

measuring, 232–234

规划, 251 , 317 , 423

planning, 251, 317, 423

容量测试

Capacity testing

和金丝雀发布,264

and canary releasing, 264

和云计算,313

and cloud computing, 313

和虚拟化,310

and virtualization, 310

作为测试策略的一部分,91

as part of a testing strategy, 91

自动化,238–244

automating, 238–244

环境,234–237

environment for, 234–237

外推,234

extrapolating, 234

在部署管道112、244–246

in the deployment pipeline, 112, 244–246

中的交互模板,241-244

interaction templates in, 241–244

测量值,232–234

measurements for, 232–234

分布式系统,240

of distributed systems, 240

性能,238

performance of, 238

场景,238

scenarios in, 238

模拟,239

simulations for, 239

测试数据管理,341-342

test data managing in, 341–342

阈值,238

thresholds in, 238

通过服务层,239

through a service layer, 239

通过 API,239

through the API, 239

通过 UI,240–241

through the UI, 240–241

热身时间,245

warm-up periods in, 245

卡皮斯特拉诺, 162

Capistrano, 162

谨慎乐观,370-371

Cautious optimism, 370–371

CCTV(闭路电视),273

CCTV (Closed-circuit television), 273

CfEngine, 51 , 53 , 155 , 161 , 284 , 287 , 291

CfEngine, 51, 53, 155, 161, 284, 287, 291

变革管理, 9 , 53–54 , 280 , 287 , 421 , 429 , 436–437 , 440–441

Change management, 9, 53–54, 280, 287, 421, 429, 436–437, 440–441

更改请求,440

Change request, 440

变更集。 修订

Changeset. See Revision

检查点,394

Check point, 394

登记入住

Checking in

和提交测试的持续时间,185

and duration of commit tests, 185

频率,435

frequency, 435

在破损的建筑上,66

on a broken build, 66

检查样式, 74 , 158

CheckStyle, 74, 158

厨师,291

Chef, 291

樱桃采摘, 394 , 409 , 414

Cherry picking, 394, 409, 414

数鸡,254

Chicken-counting, 254

CIM(通用信息模型),319

CIM (Common Information Model), 319

CIMA(特许管理会计师协会),417

CIMA (Chartered Institute of Management Accountants), 417

断路器模式, 98 , 211

Circuit Breaker pattern, 98, 211

循环依赖,371–373

Circular dependencies, 371–373

类加载器,354

Classloader, 354

ClearCase, 385–386 , 399 , 404 , 409

ClearCase, 385–386, 399, 404, 409

云计算

Cloud computing

和建筑, 313 , 315

and architecture, 313, 315

和合规性,314

and compliance, 314

和非功能性需求,314

and nonfunctional requirements, 314

和性能,314

and performance, 314

和安全,313

and security, 313

和服务级别协议,314

and service-level agreements, 314

和供应商锁定,315

and vendor lock-in, 315

批评,316-317

criticisms of, 316–317

312的定义

definition of, 312

用于验收测试,220–222

for acceptance tests, 220–222

云中的基础设施,313–314

infrastructure in the Cloud, 313–314

云端平台,314–315

platforms in the Cloud, 314–315

CMS(配置管理系统),290

CMS (configuration management system), 290

补鞋匠,289

Cobbler, 289

代码分析, 120 , 135

Code analysis, 120, 135

代码覆盖率135,172

Code coverage, 135, 172

代码重复,121

Code duplication, 121

代码冻结,408

Code freeze, 408

代码风格,121

Code style, 121

合作

Collaboration

临时, 8

ad-hoc, 8

和验收测试, 99 , 190

and acceptance tests, 99, 190

和分布式版本控制,395

and distributed version control, 395

和部署管道,107

and the deployment pipeline, 107

作为以下目标:

as a goal of:

组件,346

components, 346

版本控制, 32 , 381

version control, 32, 381

参与交付团队之间18、434、434、436 _

between teams involved in delivery, 18, 434, 434, 436

在孤立的组织中,439

in siloed organizations, 439

COM(组件对象模型),353

COM (Component Object Model), 353

现成的商业软件。参见 COTS

Commercial, off-the-shelf software. See COTS

提交消息,37–38

Commit messages, 37–38

提交阶段

Commit stage

和增量开发,347

and incremental development, 347

和测试数据,338–339

and test data, 338–339

作为...的一部分:

as part of:

CI, 61

CI, 61

部署管道110,120–122

deployment pipeline, 110, 120–122

脚本, 152

scripting, 152

工作流程, 169

workflow, 169

提交测试

Commit tests

的特点, 14

characteristics of, 14

失败, 73 , 171

failing, 73, 171

177-185的原则和实践

principles and practices of, 177–185

入住前跑步,66–67

running before checking in, 66–67

速度60–62 , 73 , 435

speed of, 60–62, 73, 435

测试数据管理,338–339

test data managing in, 338–339

另见 单元测试

See also Unit tests

兼容性测试,342

Compatibility testing, 342

汇编

Compilation

作为提交阶段的一部分,120

as part of commit stage, 120

增量,146

incremental, 146

优化, 146

optimizing, 146

静态,353

static, 353

警告,74

warnings, 74

遵守

Compliance

和云计算,314

and cloud computing, 314

和持续交付,267

and continuous delivery, 267

和图书馆管理,160

and library management, 160

和组织成熟度,420

and organizational maturity, 420

作为版本控制的目标,31

as a goal of version control, 31

管理,436–441

managing, 436–441

组件测试,89

Component tests, 89

和CI,60

and CI, 60

组件

Components

和部署,156

and deployment, 156

和项目结构,160

and project structure, 160

和部署管道,360–361

and the deployment pipeline, 360–361

39 , 356–360 , 363的配置管理

configuration management of, 39, 356–360, 363

创造,356–360

creating, 356–360

定义,345

definition of, 345

39 , 375的依赖管理

dependency management of, 39, 375

对于发布分支,409

for branch by release, 409

与图书馆,352

vs. libraries, 352

手风琴, 85 , 191 , 196

Concordion, 85, 191, 196

配置管理

Configuration management

和部署,154

and deployment, 154

和部署脚本,155

and deployment scripting, 155

和紧急修复,266

and emergency fixes, 266

和基础设施, 283–287 , 290–295

and infrastructure, 283–287, 290–295

和服务资产,421

and service asset, 421

作为发布策略的一部分,250

as part of release strategy, 250

不好,435–436

bad, 435–436

的定义,31

definition of, 31

部署时间,42

for deployment time, 42

的重要性,18-20

importance of, 18–20

手动配置管理反模式,9–10

manual configuration management antipattern, 9–10

419–421的成熟度模型

maturity model of, 419–421

迁移, 129

migrating, 129

二进制文件,373

of binaries, 373

数据库,328–329

of databases, 328–329

环境, 277 , 288 , 308

of environments, 277, 288, 308

中间件数量,295–300

of middleware, 295–300

服务器数量,288–295

of servers, 288–295

软件, 39

of software, 39

虚拟环境,305–307

of virtual environments, 305–307

推广, 257

promoting, 257

运行时间, 42 , 348 , 351

runtime, 42, 348, 351

的版本控制实践。请参见 版本控制实践

version control practices for. See Version control practices

配置管理系统。 内容管理系统

Configuration management system. See CMS

一致性,417

Conformance, 417

一致性,290

Consistency, 290

控制台输出,171

Console output, 171

合并

Consolidation

提供 CI 作为中央服务,76

providing CI as a central service, 76

通过虚拟化,304

through virtualization, 304

上下文查询,90

Contextual enquiry, 90

持续部署126、266–270、279、440 _ _ _ _

Continuous deployment, 126, 266–270, 279, 440

持续改进, 15 , 28–29 , 441

Continuous improvement, 15, 28–29, 441

持续集成管道,110

Continuous integation pipeline, 110

持续集成 (CI)

Continuous integration (CI)

和分支, 36 , 390–393 , 410 , 414

and branching, 36, 390–393, 410, 414

和数据库脚本,326–327

and database scripting, 326–327

和主线开发,405

and mainline development, 405

和测试数据管理,339

and test data management, 339

作为集中式服务,75–76

as a centralized service, 75–76

作为项目启动的一部分424、430

as part of project initiation, 424, 430

作为质量的先决条件,427

as prerequisite for quality, 427

不好,435

bad, 435

57-59的基本做法

basic practices of, 57–59

定义,55

definition of, 55

66-71的基本做法

essential practices of, 66–71

63-65中的反馈机制

feedback mechanisms in, 63–65

管理环境,289

managing environments in, 289

基于流的版本控制,403–404

with stream-based version control, 403–404

控制层,161

ControlTier, 161

康威定律,359

Conway’s Law, 359

Maven 中的坐标,375

Coordinates in Maven, 375

公司治理。 治理

Corporate governance. See Governance

成本效益分析,420

Cost-benefit analysis, 420

COTS (商业现成软件284、295、307

COTS (Commercial, off-the-shelf software), 284, 295, 307

耦合

Coupling

分析, 121 , 135 , 139 , 174

analysis of, 121, 135, 139, 174

和松耦合架构,315

and loosely coupled architecture, 315

和主线开发,392

and mainline development, 392

数据库迁移到应用程序更改329、333–334

database migrations to application changes, 329, 333–334

验收测试的外部系统,211

external systems to acceptance tests, 211

在容量测试中,242

in capacity tests, 242

数据测试,336

tests to data, 336

验收测试UI 125、192、201

UI to acceptance tests, 125, 192, 201

在发布过程中,261 , 325

within the release process, 261, 325

CPAN(综合 Perl 存档网络),155

CPAN (Comprehensive Perl Archive Network), 155

崩溃报告,267–270

Crash reports, 267–270

定时任务表,294

Crontab, 294

横切关注点,227

Crosscutting concerns, 227

跨职能需求,226

Cross-functional requirements, 226

跨职能团队105,358

Cross-functional teams, 105, 358

跨功能测试。请参见 非功能性测试

Cross-functional tests. See Nonfunctional tests

CruiseControl 系列, 58 , 127

CruiseControl family, 58, 127

黄瓜, 85–86 , 191 , 196 , 200 , 323

Cucumber, 85–86, 191, 196, 200, 323

黄瓜-Nagios,323

Cucumber-Nagios, 323

客户,422

Customer, 422

CVS(并发版本系统32,382–383,409

CVS (Concurrent Versions System), 32, 382–383, 409

周期

Cycle time

和金丝雀发布,263

and canary releasing, 263

和合规性,437

and compliance, 437

和紧急修复,266

and emergency fixes, 266

和组织成熟度,419

and organizational maturity, 419

对于基础设施的改变287、441

for changes to infrastructure, 287, 441

的重要性, 11 , 138

importance of, 11, 138

测量, 137

measuring, 137

圈复杂度, 121 , 135 , 139 , 174

Cyclomatic complexity, 121, 135, 139, 174

D

DAG(有向无环图363、400

DAG (directed acyclic graph), 363, 400

Darcs(Darcs 高级修订控制系统),396

Darcs (Darcs Advanced Revision Control System), 396

达尔文港口,294

Darwin Ports, 294

仪表盘

Dashboards

和CI,82

and CI, 82

用于操作,320–322

for operations, 320–322

用于跟踪交付状态429、440

for tracking delivery status, 429, 440

的重要性,16

importance of, 16

数据

Data

和回滚,259

and rollback, 259

生产归档, 282 , 343

archiving in production, 282, 343

在验收测试中,204

in acceptance tests, 204

生命周期,325

lifecycle of, 325

数据中心自动化工具,284

Data center automation tools, 284

数据中心管理,290–295

Data center management, 290–295

数据迁移, 118 , 129 , 262 , 264

Data migration, 118, 129, 262, 264

作为测试的一部分,257

as part of testing, 257

作为发布计划的一部分,252

as part of the release plan, 252

数据结构

Data structures

和应用性能,230

and application performance, 230

和测试,184

and tests, 184

数据库管理员, 326 , 329

Database administrators, 326, 329

数据库

Databases

配器329–331,333

and orchestration, 329–331, 333

并测试原子性,205

and test atomicity, 205

和单元测试179-180,335-336

and unit testing, 179–180, 335–336

用于中间件配置,299

for middleware configuration, 299

334的向前和向后兼容性

forward and backward compatibility of, 334

增量变化,327-331

incremental changing, 327–331

初始化,326–327

initializing, 326–327

内存中,154

in-memory, 154

迁移,327–334

migrating, 327–334

监控, 318

monitoring, 318

规范化和非规范化,331

normalization and denormalization, 331

主键,329

primary keys in, 329

重构, 334 , 341

refactoring, 334, 341

参考约束,329

referential constraints, 329

回滚, 328 , 331–334

rolling back, 328, 331–334

向前滚动,328

rolling forward, 328

中的模式,327

schemas in, 327

329332中的临时表

temporary tables in, 329, 332

交易记录和回放,332

transaction record-and-playback in, 332

升级, 261

upgrading, 261

版本控制,328–329

versioning, 328–329

数据库部署328、331、344 _ _

DbDeploy, 328, 331, 344

DbDeploy.NET,328

DbDeploy.NET, 328

数据库差异,328

DbDiff, 328

迁移,328

Dbmigrate, 328

死锁,136

Deadlock, 136

Debian, 154 , 283–284 , 353

Debian, 154, 283–284, 353

声明式部署工具,161

Declarative deployment tools, 161

声明式基础设施管理,290

Declarative infrastructure management, 290

声明式编程,147–148 另见 AntMake

Declarative programming, 147–148 See also Ant, Make

缺陷

Defects

和发布策略,251

and the release strategy, 251

作为不良 CI 的症状,435

as a symptom of poor CI, 435

关键, 131 , 265–266 , 409

critical, 131, 265–266, 409

在积压中,99-101

in backlogs, 99–101

测量, 138

measuring, 138

复制, 247

reproducing, 247

零, 100

zero, 100

戴明循环, 28 , 420 , 440

Deming cycle, 28, 420, 440

戴明,W. 爱德华兹,27岁,83 岁

Deming, W. Edwards, 27, 83

依赖关系

Dependencies

使用 Maven 进行分析,378

analyzing with Maven, 378

和整合,370

and integration, 370

和可追溯性,363

and traceability, 363

分支机构之间,391

between branches, 391

构建时间,352

build time, 352

圆形,371–373

circular, 371–373

下游, 364

downstream, 364

流体,370

fluid, 370

守卫, 370

guarded, 370

在构建工具中,146

in build tools, 146

在软件中,351-356

in software, 351–356

在项目计划中,348

in the project plan, 348

使用 Maven 进行管理,375–378

managing with Maven, 375–378

重构, 377

refactoring, 377

运行时,352

runtime, 352

静态,370

static, 370

传递性,355

transitive, 355

上游,364

upstream, 364

依赖图

Dependency graphs

保持浅浅,371

keeping shallow, 371

管理, 355 , 363–373

managing, 355, 363–373

使用部署管道建模,365–369

modeling with the deployment pipeline, 365–369

依赖地狱352–354,365

Dependency hell, 352–354, 365

依赖注入

Dependency injection

和抽象分支,351

and branch by abstraction, 351

和假装时间,184

and faking time, 184

和马文,149

and Maven, 149

和单元测试,179–180

and unit testing, 179–180

依赖管理, 38–39 , 149 , 353

Dependency management, 38–39, 149, 353

和信任,369

and trust, 369

在应用程序和基础设施之间,285

between applications and infrastructure, 285

依赖网络和构建工具,144

Dependency networks and build tools, 144

部署

Deployment

和组件,357

and components, 357

和幂等性,155–156

and idempotence, 155–156

自动化,152–153

automating, 152–153

蓝绿。请参阅 蓝绿部署

blue-green. See Blue-green deployment

定义,25

definition of, 25

从头开始部署一切,156

deploy everything from scratch, 156

一起部署一切,156

deploy everything together, 156

快速失败,272–273

fail fast, 272–273

失败的, 117

failures of, 117

增量实施,156-157

incremental implementation of, 156–157

后期部署反模式,7–9

late deployment antipattern, 7–9

日志记录,270–271

logging, 270–271

管理, 421

managing, 421

手动5–7、116、165 _ _ _

manual, 5–7, 116, 165

编排, 161

orchestrating, 161

规划和实施,253-254

planning and implementing, 253–254

脚本,160–164

scripting, 160–164

脚本升级,153

scripting upgrades, 153

烟雾测试, 117 , 163

smoke-testing, 117, 163

通过自动化测试130,153

testing through automation, 130, 153

到远程机器,161

to remote machines, 161

每个环境使用相同过程22、115–117、153–154、253、279、283、286、308、438 _ _ _ _ _ _ _ _ _

use the same process for every environment, 22, 115–117, 153–154, 253, 279, 283, 286, 308, 438

验证环境,155

validating environments, 155

部署流水线

Deployment pipeline

验收测试阶段,213-218

acceptance test stage, 213–218

和工件存储库,374–375

and artifact repositories, 374–375

和发布分支,409

and branch for release, 409

和能力测试,244-246

and capacity tests, 244–246

和合规性,437

and compliance, 437

和组件, 360–361 , 361–363

and components, 360–361, 361–363

和持续部署,267

and continuous deployment, 267

和数据库,326

and databases, 326

和依赖图,365–369

and dependency graphs, 365–369

和紧急修复,266

and emergency fixes, 266

和治理, 418 , 442

and governance, 418, 442

和集成测试,212

and integration tests, 212

和主线开发,405

and mainline development, 405

和测试数据,338–343

and test data, 338–343

和版本控制404,416

and version control, 404, 416

和虚拟304,307–310

and virtualization, 304, 307–310

和虚拟机模板,309

and VM templates, 309

作为项目启动的一部分,430

as part of project initiation, 430

106–113的定义

definition of, 106–113

136–137的演变

evolution of, 136–137

失败,119–120

failing, 119–120

实施,133-137

implementing, 133–137

在孤立的组织中,439

in siloed organizations, 439

词源,122

origin of term, 122

脚本, 152

scripting, 152

部署生产线,110

Deployment production line, 110

部署测试89,216–218,285 _ _

Deployment tests, 89, 216–218, 285

开发和发布,425–428

Develop and release, 425–428

开发环境

Development environments

和验收测试,125

and acceptance tests, 125

和部署脚本,154

and deployment scripts, 154

和测试数据,343

and test data, 343

33、50、289配置管理_ _

configuration management of, 33, 50, 289

管理作为发展的一部分,62

managing as part of development, 62

用于 GUI 测试的设备驱动程序,202

Device drivers for GUI testing, 202

开发运营,28 岁

DevOps, 28

和灵活的基础设施,279

and agile infrastructure, 279

创建部署过程,270

creating the deployment process, 270

构建系统的所有权,174

ownership of the build system, 174

另见 操作

See also Operations

DHCP(动态主机配置协议285、289

DHCP (Dynamic Host Configuration Protocol), 285, 289

诊断,139

Diagnostics, 139

钻石依存关系354,365

Diamond dependencies, 354, 365

有向无环图。参见 DAG

Directed acyclic graph. See DAG

目录服务,300

Directory services, 300

灾难恢复, 250 , 282

Disaster recovery, 250, 282

纪律

Discipline

和验收测试,214

and acceptance tests, 214

和CI,57

and CI, 57

和增量开发, 349 , 392 , 426 , 434

and incremental development, 349, 392, 426, 434

磁盘映像,305

Disk images, 305

显示。查看 仪表板

Displays. See Dashboards

分布式开发

Distributed development

和CI,75-78

and CI, 75–78

和流水线组件,360

and pipelining components, 360

和版本控制,78

and version control, 78

通讯,75

communication in, 75

分布式团队,143

Distributed teams, 143

分布式版本控制79-81,393-399,411,414 _ _ _

Distributed version control, 79–81, 393–399, 411, 414

DLL(动态链接库352,356

DLL (Dynamic-Link Library), 352, 356

DLL 地狱,352

DLL hell, 352

域名解析, 300

DNS, 300

DNS 区域文件,285

DNS zone files, 285

文档

Documentation

和自我记录的基础设施,292

and self-documenting infrastructure, 292

作为 IT 运营的要求,280–281

as a requirement of IT operations, 280–281

作为...的一部分:

as part of:

合规和审计,437

compliance and auditing, 437

发布计划,252

release plan, 252

从验收测试中生成,86

generating from acceptance tests, 86

与自动化, 287 , 437–438

vs. automation, 287, 437–438

领域语言,198

Domain language, 198

领域驱动设计,152

Domain-driven design, 152

领域特定语言 (DSL)

Domain-specific languages (DSLs)

构建工具,144–151

build tools for, 144–151

198的定义

definition of, 198

在验收测试中,198–204

in acceptance testing, 198–204

另见 人偶

See also Puppet

不要重复自己,358

Don’t repeat yourself, 358

完毕

Done

和验收测试,85

and acceptance tests, 85

和测试,101

and testing, 101

定义,27-28

definition of, 27–28

签核作为项目生命周期的一部分426、434

signoff as part of project lifecycle, 426, 434

停机时间, 260 , 436

Downtime, 260, 436

重装, 294

Dpkg, 294

虚拟对象,92

Dummy objects, 92

另见 测试替身

See also Test doubles

重复,139

Duplication, 139

动态链接,357

Dynamic linking, 357

动态视图,403

Dynamic views, 403

E

耳朵,159

EARs, 159

轻松模拟,181

EasyMock, 181

EC2, 221

EC2, 221

日蚀,350

Eclipse, 350

效率,419

Efficiency, 419

鸡蛋, 155

Eggs, 155

电动指挥官,58 岁

ElectricCommander, 58

埃里森,拉里,316

Ellison, Larry, 316

嵌入式软件, 256 , 277

Embedded software, 256, 277

紧急修复,265–266

Emergency fixes, 265–266

封装

Encapsulation

和组件,358

and components, 358

和主线开发,392

and mainline development, 392

和单片系统,345

and monolithic systems, 345

和单元测试,180

and unit testing, 180

在验收测试中,206–207

in acceptance tests, 206–207

端到端测试

End-to-end testing

验收测试,205

acceptance tests, 205

容量测试,241

capacity tests, 241

企业治理。 治理

Enterprise governance. See Governance

环境

Environments

作为发布策略的一部分,250

as part of release strategy, 250

基线, 51 , 155

baselines, 51, 155

容量测试, 234–237 , 258

capacity testing, 234–237, 258

277的定义

definition of, 277

管理, 49–54 , 130 , 277 , 288–295 , 308

managing, 49–54, 130, 277, 288–295, 308

类似生产107、117、129、254、308 _ _ _ _ _ _

production-like, 107, 117, 129, 254, 308

供应,288-290

provisioning, 288–290

版本控制的可再创造性,33

re-creatability from version control, 33

共享, 258

shared, 258

分期, 258–259 , 330

staging, 258–259, 330

系统集成测试 (SIT),330

systems integration testing (SIT), 330

等价划分,86

Equivalence partitioning, 86

逃脱, 44 , 47 , 257

Escape, 44, 47, 257

估计,428

Estimates, 428

桉树, 312 , 316

Eucalyptus, 312, 316

事件驱动系统

Event-driven systems

和组件,359

and components, 359

容量测试,241

capacity testing, 241

可执行规范195–198、246、339、342 _ _ _ _

Executable specifications, 195–198, 246, 339, 342

探索测试87、90、128、255、343 _ _ _ _ _

Exploratory testing, 87, 90, 128, 255, 343

外部系统

External systems

和验收测试125,210

and acceptance tests, 125, 210

和集成测试,96–98

and integration testing, 96–98

和日志记录,320

and logging, 320

和发布策略,250

and the release strategy, 250

配置,50

configuration of, 50

升级, 261

upgrading, 261

外部 (SVN), 384

Externals (SVN), 384

容量测试外推,234

Extrapolation in capacity testing, 234

极限编程, 26 , 266

Extreme programming, 26, 266

和 CI, 55 , 71

and CI, 55, 71

F

F

织物,162

Fabric, 162

立面图案,351

Façade pattern, 351

因素,291

Facter, 291

快速失败

Fail fast

提交阶段,171

commit stage, 171

部署,272–273

deployments, 272–273

故障转移,作为发布策略的一部分,251

Failover, as part of the release strategy, 251

假对象,92

Fake objects, 92

特色分支。请参见 版本控制实践

Feature branches. See Version control practices

特色剧组,411

Feature crews, 411

反馈

Feedback

和自动验收测试,86

and automated acceptance tests, 86

和金丝雀发布,263

and canary releasing, 263

和依赖管理,369–370

and dependency management, 369–370

和指标,137–140

and metrics, 137–140

和监控,317

and monitoring, 317

和集成管道,362

and the integration pipeline, 362

作为项目生命周期的一部分,426

as part of project lifecycle, 426

由部署管道创建,106

created by deployment pipeline, 106

的重要性,12-16

importance of, 12–16

在提交阶段,120

during commit stage, 120

通过虚拟化改进,310

improving through virtualization, 310

建模依赖关系时,365

when modeling dependencies, 365

流水线组件时,360

when pipelining components, 360

文件系统层次结构标准,165

Filesystem Hierarchy Standard, 165

文件系统,共享用于存储二进制文件,166

Filesystem, shared for storing binaries, 166

查找错误, 74 , 158

FindBugs, 74, 158

消防,286

Firefighting, 286

防火墙

Firewalls

和云计算,313

and cloud computing, 313

和集成测试,96

and integration testing, 96

配置, 118 , 284 , 300

configuration of, 118, 284, 300

适合,201

Fit, 201

适合用途, 421 , 426 , 442

Fit for purpose, 421, 426, 442

适合使用, 421 , 427

Fit for use, 421, 427

健身, 191 , 196 , 201

FitNesse, 191, 196, 201

烙饼,318

Flapjack, 318

柔性,192

Flex, 192

Force.com, 314

Force.com, 314

取证工具,301

Forensic tools, 301

分叉。请参见 版本控制实践

Forking. See Version control practices

向前兼容性,334

Forward compatibility, 334

脆弱性。请参见 验收测试

Fragility. See Acceptance tests

功能,162

Func, 162

功能测试。请参见 验收测试

Functional tests. See Acceptance tests

外汇警察,74 岁

FxCop, 74

G

G

甘特图,151

Gantt, 151

甘特图,280

Gantt charts, 280

垃圾收集,247

Garbage collection, 247

门。 审批程序

Gate. See Approval process

加夫,375

GAV, 375

宝石,155

Gems, 155

巴布亚,353

Gentoo, 353

Git, 32 , 79–81 , 374 , 393 , 396 , 403

Git, 32, 79–81, 374, 393, 396, 403

GitHub, 79 , 394 , 411

GitHub, 79, 394, 411

给定,当,然后,86 , 195 , 336

Given, when, then, 86, 195, 336

全局程序集缓存,353

Global assembly cache, 353

全局优化,138

Global optimization, 138

谷歌邮箱,313

Gmail, 313

去, 58 , 113 , 126 , 255 , 373

Go, 58, 113, 126, 255, 373

去/不去,423

Go/no-go, 423

谷歌应用引擎314–315,317

Google App Engine, 314–315, 317

谷歌代码,394

Google Code, 394

治理

Governance

商业, 417

business, 417

企业, 417

corporate, 417

企业, 417

enterprise, 417

很好,442

good, 442

GPG(GNU 隐私卫士),294

GPG (GNU Privacy Guard), 294

GPL(通用公共许可证),355

GPL (General Public License), 355

摇篮,151

Gradle, 151

绿地项目,92–94

Greenfield projects, 92–94

警卫测试,245

Guard tests, 245

GUI(图形用户界面)

GUI (Graphical user interface)

和验收测试,192–193

and acceptance tests, 192–193

用于部署,165

for deployment, 165

分层, 192

layering, 192

另请参阅 用户界面

See also UI

阿甘,371

Gump, 371

H

H

H2, 336

H2, 336

手柄,301

Handle, 301

快乐之路, 85 , 87–88 , 94

Happy path, 85, 87–88, 94

硬化,284

Hardening, 284

硬件

Hardware

和容量测试,236

and capacity testing, 236

标准化虚拟化,304

virtualization for standardization, 304

哈希114、166、175、373、438 _ _ _ _ _ _ _

Hashing, 114, 166, 175, 373, 438

霍桑效应,137

Hawthorne effect, 137

休眠,159

Hibernate, 159

隐藏功能,347–349

Hiding functionality, 347–349

高可用性

High availability

和业务连续性规划,282

and business continuity planning, 282

和多宿主服务器,302

and multihomed servers, 302

作为发布策略的一部分,251

as part of the release strategy, 251

HIPAA 314,436 _

HIPAA, 314, 436

热部署。请参见 零停机版本

Hot deployment. See Zero-downtime releases

HP惠普156、291、318

HP (Hewlett-Packard), 156, 291, 318

惠普运营中心287,296

HP Operations Center, 287, 296

哈德森,58 岁63 岁127岁,289岁

Hudson, 58, 63, 127, 289

过度活跃的构建,370

Hyperactive builds, 370

超 V,290

Hyper-V, 290

I

IANA(互联网号码分配机构),320

IANA (Internet Assigned Numbers Authority), 320

IBM, 156 , 291 , 303 , 316 , 318

IBM, 156, 291, 303, 316, 318

IDE(集成开发环境), 57 , 143 , 160

IDE (Integrated Development Environment), 57, 143, 160

幂等性

Idempotence

和部署工具,161

and deployment tools, 161

和基础设施管理290–291,295

and infrastructure management, 290–291, 295

应用程序部署,155–156

of application deployment, 155–156

鉴定,422

Identification, 422

IIS(互联网信息服务),299

IIS (Internet Information Services), 299

影响,430

Impact, 430

开始, 283 , 422–424

Inception, 283, 422–424

增量编译,146

Incremental compilation, 146

增量交付331、346–351、418、420、442 _ _ _ _ _ _

Incremental delivery, 331, 346–351, 418, 420, 442

增量开发, 36 , 326 , 346–351 , 367 , 405–406 , 425 , 434

Incremental development, 36, 326, 346–351, 367, 405–406, 425, 434

知情的悲观主义,371

Informed pessimism, 371

基础设施

Infrastructure

作为项目启动的一部分,424

as part of project initiation, 424

可审计性,287

auditability of, 287

277的定义

definition of, 277

317的演变

evolution of, 317

管理,283–287

managing, 283–287

测试变化,287

testing changes in, 287

云中的基础设施,313–314

Infrastructure in the Cloud, 313–314

启动,424–425

Initiation, 424–425

内存数据库, 154 , 180 , 336

In-memory database, 154, 180, 336

安装工,51

Installers, 51

安装屏蔽,118

InstallShield, 118

即时通讯工具,75

Instant messenger, 75

集成开发环境。 集成开发环境

Integrated Development Environment. See IDE

一体化

Integration

和验收测试,210

and acceptance tests, 210

和数据库,329

and databases, 329

和依赖性,369–370

and dependencies, 369–370

和基础设施管理,301

and infrastructure management, 301

积分阶段, 55 , 348 , 405 , 426 , 435

Integration phase, 55, 348, 405, 426, 435

集成管道,361–363

Integration pipeline, 361–363

整合团队,358

Integration team, 358

集成测试,96–98

Integration tests, 96–98

有意编程,198

Intentional programming, 198

交互模板241-244,342

Interaction templates, 241–244, 342

间歇性故障

Intermittent failures

在验收测试200、207

in acceptance tests, 200, 207

在容量测试233、245

in capacity tests, 233, 245

互操作性,316

Interoperability, 316

库存, 391 , 418

Inventory, 391, 418

控制反转。请参见 依赖注入

Inversion of control. See Dependency injection

投资原则, 93 , 190

INVEST principles, 93, 190

IPMI(智能平台管理接口288,318

IPMI (Intelligent Platform Management Interface), 288, 318

ISO 9001, 437

ISO 9001, 437

验收测试中的隔离205,220

Isolation in acceptance tests, 205, 220

问题,431

Issue, 431

迭代一,253

Iteration one, 253

迭代零,134

Iteration zero, 134

迭代交付,442

Iterative delivery, 442

和分析,193–195

and analysis, 193–195

迭代开发,425

Iterative development, 425

ITIL(信息技术基础设施库),421–422

ITIL (Information Technology Infrastructure Library), 421–422

常春藤, 150 , 154 , 160 , 166 , 355 , 375

Ivy, 150, 154, 160, 166, 355, 375

J

J2EE(Java 2 平台,企业版),359

J2EE (Java 2 Platform, Enterprise Edition), 359

JAR 159,356,374 _ _ _

JARs, 159, 356, 374

爪哇

Java

用 Ant 构建,147

building with Ant, 147

类加载器,354

classloader in, 354

组件,345

components in, 345

数据库迁移,328

database migration in, 328

命名约定,158

naming conventions in, 158

项目结构,157–160

project structure in, 157–160

运行时依赖,354

runtime dependencies in, 354

爪哇,146

Javac, 146

Java数据库,336

JavaDB, 336

Javadoc, 149

Javadoc, 149

JBehave 85,191,196 _ _ _

JBehave, 85, 191, 196

JDepend,74 岁

JDepend, 74

吉克斯,146岁

Jikes, 146

杰米特,243

JMeter, 243

杰莫克,181岁

JMock, 181

江淮,319

JMX, 319

红宝石, 151

JRuby, 151

快速启动, 284 , 289

Jumpstart, 284, 289

即时编译器,146

Just-in-time compiler, 146

K

改善。查看 持续改进

Kaizen. See Continuous improvement

看板,411

Kanban, 411

启动会议,194

Kick-off meetings, 194

启动, 284 , 289

Kickstart, 284, 289

高德纳,唐纳德,228

Knuth, Donald, 228

大号

L

标签,374

Label, 374

大型团队

Large teams

和主线开发, 392 , 405

and mainline development, 392, 405

按团队划分,412

branch by team, 412

发布分支,409

branch for release, 409

通过组件协作,346

collaboration through components in, 346

另见 团队规模

See also Team size

得墨法则345、358、406

Law of Demeter, 345, 358, 406

图层

Layers

在验收测试中,190

in acceptance tests, 190

在软件中,359

in software, 359

LCCFG,291

LCFG, 291

LDAP(轻量级目录访问协议), 44 , 291

LDAP (Lightweight Directory Access Protocol), 44, 291

倾斜

Lean

和项目管理,427

and project management, 427

作为持续交付的原则,27

as a principle of continuous delivery, 27

对本书的影响,16

influence on this book, 16

不连续交付的成本,418

the cost of not delivering continuously, 418

遗留系统, 95–96 , 306

Legacy systems, 95–96, 306

图书馆

Libraries

配置管理, 38–39 , 354–356 , 363

configuration management of, 38–39, 354–356, 363

352的定义

definition of, 352

依赖管理,375

dependency management of, 375

管理作为发展的一部分,62

managing as part of development, 62

许可

Licensing

作为发布计划的一部分,252

as part of the release plan, 252

中间件,300

of middleware, 300

生命周期,421–429

Lifecycle, 421–429

可能性,430

Likelihood, 430

代码行数,137

Lines of code, 137

Linux, 154 , 310 , 395

Linux, 154, 310, 395

直播发布。请参阅 蓝绿部署

Live-live releases. See Blue-green deployments

生活建筑,110

Living build, 110

负载测试,231

Load testing, 231

锁定。请参见 版本控制实践

Locking. See Version control practices

记录

Logging

和基础设施管理,301

and infrastructure management, 301

和发布策略,250

and the release strategy, 250

作为运营团队的要求,281

as a requirement of operations team, 281

的重要性,436

importance of, 436

部署,270–271

of deployment, 270–271

基础设施变化,287

of infrastructure changes, 287

LOM(熄灯管理)288,318

LOM (Lights Out Management), 288, 318

寿命测试, 231 , 238

Longevity tests, 231, 238

洛夫,301

Lsof, 301

M

苹果操作系统,310

Mac OS, 310

主线开发, 35–37 , 59 , 346–351 , 392 , 405–408

Mainline development, 35–37, 59, 346–351, 392, 405–408

可维护性

Maintainability

和主线开发,406

and mainline development, 406

和质量,434

and quality, 434

验收测试,190–192

of acceptance tests, 190–192

容量测试,240

of capacity tests, 240

维护

Maintenance

作为发布策略的一部分250、409

as part of release strategy, 250, 409

构建系统,174

of the build system, 174

使, 144 , 146–147

Make, 144, 146–147

生成文件,146

Makefile, 146

托管设备,319

Managed devices, 319

管理信息库,320

Management information base, 320

清单

Manifests

和可追溯性,166

and traceability, 166

硬件, 271

of hardware, 271

手动测试, 110 , 126 , 189 , 223 , 343

Manual testing, 110, 126, 189, 223, 343

马拉松,243

Marathon, 243

布赖恩·马里克,84 岁

Marick, Brian, 84

马林巴琴,155

Marimba, 155

木偶集体, 161 , 291

Marionette Collective, 161, 291

市场营销,252

Marketing, 252

成熟度模型,419–421

Maturity model, 419–421

Maven, 38 , 148–150 , 154 , 157 , 160 , 166 , 355 , 375–378

Maven, 38, 148–150, 154, 157, 160, 166, 355, 375–378

分析依赖关系,378

analyzing dependencies with, 378

与 Buildr 相比,151

compared to Buildr, 151

坐标,375

coordinates in, 375

375的存储库

repository of, 375

快照,377

snapshots in, 377

子项目,158

subprojects in, 158

Maven 标准目录布局,157

Maven Standard Directory Layout, 157

麦卡锡,约翰,312

McCarthy, John, 312

平均故障间隔时间。参见 平均故障间隔时间

Mean time between failures. See MTBF

平均修复时间。 平均修复时间

Mean time to repair. See MTTR

测量, 264 , 420

Measurement, 264, 420

内存泄漏,247

Memory leaks, 247

Mercurial, 32 , 79–81 , 374 , 393 , 396 , 398 , 403

Mercurial, 32, 79–81, 374, 393, 396, 398, 403

合并冲突386、390、415 _ _

Merge conflicts, 386, 390, 415

合并团队,407

Merge team, 407

合并

Merging

389–390的定义

definition of, 389–390

在按功能划分的分支中349、410

in branch by feature, 349, 410

在团队分支中,413

in branch by team, 413

在 ClearCase 中,404

in ClearCase, 404

在基于流的系统中,402

in stream-based systems, 402

在整合阶段,406

in the integration phase, 406

追踪, 385

tracking, 385

分布式版本控制,399

with distributed version control, 399

带乐观锁定,386

with optimistic locking, 386

消息队列

Message queues

作为 API,357

as an API, 357

容量测试,241

capacity testing, 241

配置管理,296

configuration management of, 296

元数据库,299

Metabase, 299

指标, 106 , 172 , 287 , 441

Metrics, 106, 172, 287, 441

作为部署管道的一部分,137–140

as part of deployment pipeline, 137–140

微软, 316 , 359

Microsoft, 316, 359

中间件

Middleware

和应用程序部署,155

and application deployment, 155

配置管理,295–300

configuration management of, 295–300

管理, 130 , 284

managing, 130, 284

监控, 318

monitoring, 318

缓解,430

Mitigation, 430

摩卡咖啡,181

Mocha, 181

莫基托,181岁

Mockito, 181

莫克斯, 92 , 178

Mocks, 92, 178

另见 测试替身

See also Test doubles

监控

Monitoring

和商业智能,317

and business intelligence, 317

申请, 318

applications, 318

作为发布策略的一部分,250

as part of the release strategy, 250

的重要性,436

importance of, 436

基础设施和环境,317–323

infrastructure and environments, 317–323

中间件,318

middleware, 318

网络,302

network for, 302

操作系统, 318

operating systems, 318

281–282的要求

requirements for, 281–282

用户行为,318

user behavior, 318

单体架构345,357

Monolithic architecture, 345, 357

单调,396

Monotone, 396

MSBuild,148

MSBuild, 148

MTBF(平均无故障时间280、286、440

MTBF (mean time between failures), 280, 286, 440

MTTR平均修复时间278、280、286、440

MTTR (mean time to repair), 278, 280, 286, 440

多宿主系统,301–303

Multihomed systems, 301–303

神话英雄,108

Mythical hero, 108

N

纳巴兹塔格,63 岁

Nabaztag, 63

Nagios, 257 , 281 , 301 , 318 , 321

Nagios, 257, 281, 301, 318, 321

南特,148岁

Nant, 148

依赖,74

NDepend, 74

。网

.NET

验收测试,197

acceptance tests in, 197

和依赖地狱,353

and dependency hell, 353

数据库迁移,328

database migration in, 328

项目结构,157–160

project structure in, 157–160

提示和技巧,167

tips and tricks for, 167

网络引导,289

Network boot, 289

网管系统,319

Network management system, 319

网络

Networks

管理, 302

administration of, 302

和非功能性需求,229

and nonfunctional requirements, 229

配置管理,300

configuration management of, 300

118的拓扑结构

topology of, 118

虚拟, 311

virtual, 311

联系, 111 , 166 , 175 , 355 , 361 , 373 , 375

Nexus, 111, 166, 175, 355, 361, 373, 375

NIC(网络接口卡),302

NICs (Network Interface Cards), 302

每晚构建, 65 , 127

Nightly build, 65, 127

181

NMock, 181

非功能性需求

Nonfunctional requirements

分析, 226–228

analysis of, 226–228

和验收标准,227-228

and acceptance criteria, 227–228

和云计算,314

and cloud computing, 314

和部署管道,136

and the deployment pipeline, 136

伐木,320

logging, 320

管理, 226–228 , 436

managing, 226–228, 436

发布策略作为来源,251

release strategy as a source of, 251

权衡,227

trade-offs for, 227

用于测试的虚拟化,305

virtualization for testing, 305

非功能测试

Nonfunctional tests

的定义,91

definition of, 91

在部署管道中,128

in the deployment pipeline, 128

非数据库, 326

NoSQL, 326

通知

Notification

和CI,63-65

and CI, 63–65

作为监控的一部分,317

as part of monitoring, 317

N层架构

N-tier architecture

和组件,359

and components, 359

和部署,155

and deployment, 155

烟雾测试,164

smoke-testing, 164

O

面向对象设计,350

Object-oriented design, 350

开源,143

Open source, 143

和分布式版本控制,81

and distributed version control, 81

和马文,375

and Maven, 375

OpenNMS, 281 , 301 , 318

OpenNMS, 281, 301, 318

操作系统

Operating systems

配置,118

configuration of, 118

监控, 318

monitoring, 318

操作, 105 , 279–283 , 428–429

Operations, 105, 279–283, 428–429

另见 DevOps

See also DevOps

运营中心,291

Operations Center, 291

运营经理, 281 , 301 , 318

Operations Manager, 281, 301, 318

机会成本,300

Opportunity cost, 300

乐观锁定,386–387

Optimistic locking, 386–387

甲骨文, 154 , 320

Oracle, 154, 320

配器, 257–258 , 329–331 , 333

Orchestration, 257–258, 329–331, 333

组织变革,419

Organizational change, 419

OSGi, 350 , 354–356

OSGi, 350, 354–356

带外管理, 288 , 318

Out-of-band management, 288, 318

过度设计,228

Overdesign, 228

P

P

包装, 296

Packaging, 296

和配置, 41

and configuration, 41

作为...的一部分:

as part of:

部署管道135,283

deployment pipeline, 135, 283

整合, 361

integration, 361

工具,154–155

tools for, 154–155

全景密码,139

Panopticode, 139

密码。 安全

Passwords. See Security

补丁,251

Patches, 251

模式和非功能性需求,230

Patterns and nonfunctional requirements, 230

PCI DSS, 314 , 436

PCI DSS, 314, 436

高峰需求,244

Peak demand, 244

Perforce,385

Perforce, 385

表现

Performance

和治理,417

and governance, 417

225的定义

definition of, 225

验收测试,218–222

of acceptance tests, 218–222

调整,247

tuning, 247

Perl, 155 , 283 , 356

Perl, 155, 283, 356

悲观锁定,386–387

Pessimistic locking, 386–387

试点项目,428

Pilot projects, 428

计划、执行、检查、行动。参见 戴明循环

Plan, do, check, act. See Deming cycle

云端平台,314–315

Platforms in the Cloud, 314–315

聚甲醛,375

POM, 375

后缀,293

Postfix, 293

波将金村,351

Potemkin village, 351

电力建设者,271

PowerBuilder, 271

PowerShell, 162 , 282 , 299

PowerShell, 162, 282, 299

验收测试的先决条件,206

Preconditions in acceptance tests, 206

可预测性,419

Predictability, 419

过早的优化,228

Premature optimization, 228

预置, 284 , 289

Preseed, 284, 289

测试提交37、67、120、171 _ _ _

Pretested commit, 37, 67, 120, 171

定价,252

Pricing, 252

主键,329

Primary keys, 329

优先次序

Prioritization

作为项目生命周期的一部分,427

as part of project lifecycle, 427

缺陷数,101

of defects, 101

非功能性需求,226

of nonfunctional requirements, 226

要求,422

of requirements, 422

进程边界

Process boundaries

和验收测试,206

and acceptance tests, 206

和非功能性需求,229

and nonfunctional requirements, 229

过程建模,133

Process modeling, 133

采购,283

Procurement, 283

产品负责人,422

Product owner, 422

生产环境

Production environments

和不受控制的变化,273

and uncontrolled changes, 273

登录, 160

logging in to, 160

生产准备346–351,426

Production readiness, 346–351, 426

生产规模,251

Production sizing, 251

生产环境107、117、129、308 _ _ _

Production-like environments, 107, 117, 129, 308

254的特征

characteristics of, 254

生产力, 50 , 82 , 173

Productivity, 50, 82, 173

面向产品的构建工具,145

Product-oriented build tools, 145

分析工具,231

Profiling tools, 231

盈利能力,419

Profitability, 419

项目地平线,423

Project horizon, 423

项目经理,428

Project managers, 428

JVM 和 .NET 项目的项目结构,157–160

Project structure for JVM and .NET projects, 157–160

混杂整合,81

Promiscuous integration, 81

晋升, 46 , 254–257 , 402 , 406

Promotion, 46, 254–257, 402, 406

概念证明,420

Proof of concept, 420

供应, 288 , 290–295 , 303

Provisioning, 288, 290–295, 303

普赛克,151

Psake, 151

执行程序,162

PsExec, 162

拉动系统17、106、255 _ _

Pull system, 17, 106, 255

脉冲,58

Pulse, 58

木偶, 51 , 53 , 118 , 155–156 , 161 , 284 , 287–288 , 290–296 , 300 , 306 , 323

Puppet, 51, 53, 118, 155–156, 161, 284, 287–288, 290–296, 300, 306, 323

按钮部署, 17 , 112 , 126 , 135 , 157 , 255 , 315

Push-button deployment, 17, 112, 126, 135, 157, 255, 315

PVCS(Polytron 版本控制系统),386

PVCS (Polytron Version Control System), 386

PXE(预引导执行环境),288–290

PXE (Preboot eXecution Environment), 288–290

蟒蛇, 147 , 155 , 283

Python, 147, 155, 283

Q

质量, 12 , 62 , 418 , 422 , 434–435

Quality, 12, 62, 418, 422, 434–435

227的属性

attributes of, 227

质量分析师。查看 测试人员

Quality analysts. See Testers

量词,376

Quantifiers, 376

R

R

比赛条件,136

Race condition, 136

突袭,374

RAID, 374

耙子, 150 , 150–151

Rake, 150, 150–151

建造者,305

rBuilder, 305

RCS(修订控制系统32,382

RCS (Revision Control System), 32, 382

RDBMS(关系数据库管理系统314,326

RDBMS (Relational Database Management System), 314, 326

变基, 394 , 414

Rebasing, 394, 414

录音回放

Record-and-playback

用于验收测试191,197

for acceptance testing, 191, 197

用于容量测试239,241

for capacity testing, 239, 241

数据库事务,332

of database transactions, 332

恢复点目标,282

Recovery point objective, 282

恢复时间目标,282

Recovery time objective, 282

重新部署作为退出的一种方式132、259–260

Redeployment as a way of backing out, 132, 259–260

红帽 Linux, 154 , 284

RedHat Linux, 154, 284

重构

Refactoring

验收测试, 192 , 218–219

acceptance tests, 192, 218–219

和抽象分支,350

and branch by abstraction, 350

和团队分支,415

and branch by team, 415

和CI,72

and CI, 72

和主线开发,406

and mainline development, 406

和版本控制,36

and version control, 36

作为项目生命周期的一部分,426

as part of project lifecycle, 426

作为质量的先决条件,427

as prerequisite for quality, 427

通过回归测试启用,87

enabled by regression tests, 87

参考约束,329

Referential constraints, 329

回归错误

Regression bugs

和持续交付,349

and continuous delivery, 349

作为应用程序质量差的症状,434

as a symptom of poor application quality, 434

由不受控制的变化引起,265

caused by uncontrolled changes, 265

在遗留系统上,96

on legacy systems, 96

回归测试, 87 , 124 , 128 , 189

Regression tests, 87, 124, 128, 189

构建脚本中的相对路径,164

Relative paths in build scripts, 164

发布

Release

作为部署管道的一部分,110

as part of deployment pipeline, 110

自动化, 129

automating, 129

维护, 409

maintenance of, 409

管理, 107 , 419–421

managing, 107, 419–421

建模过程,254-257

modeling the process of, 254–257

零停机时间,260–261

zero-downtime, 260–261

发布分支。请参见 版本控制实践

Release branches. See Version control practices

发布候选

Release candidate

和验收测试门,124

and acceptance test gate, 124

和手动测试阶段,127

and manual test stages, 127

22-24的定义

definition of, 22–24

生命周期,132

lifecycle of, 132

发布计划, 129 , 251–252 , 281 , 283 , 423

Release plan, 129, 251–252, 281, 283, 423

发布策略250–252、423、430 _ _

Release strategy, 250–252, 423, 430

整治,441

Remediation, 441

远程安装,288

Remote installation, 288

重复性,354

Repeatability, 354

报告状态,429

Reporting status, 429

存储库模式,335

Repository pattern, 335

再现性,373

Reproduceability, 373

要求

Requirements

运营团队成员,279–283

of the operations team, 279–283

发布策略作为来源,251

release strategy as a source of, 251

弹性,316

Resilience, 316

资源状况,136

Resources condition, 136

责任

Responsibility

用于部署,271

for deployment, 271

用于修复构建, 70–71 , 174

for fixing the build, 70–71, 174

了解运营的开发人员,281

of developers to understand operations, 281

休息,197

Rest, 197

回顾,16

Retrospectives, 16

作为...的一部分:

as part of:

持续改进, 28 , 420 , 441

continuous improvement, 28, 420, 441

风险管理,431

risk management, 431

实现协作,440

to enable collaboration, 440

收入264,316–317 _

Revenue, 264, 316–317

反向代理,271

Reverse proxy, 271

逆向工程,299

Reverse-engineering, 299

恢复,435

Reverting, 435

当构建被破坏时,69

when the build is broken, 69

修订控制。请参见 版本控制

Revision control. See Version control

修订,二进制文件,166

Revision, of binaries, 166

犀牛,181

Rhino, 181

风险

Risk

和金丝雀发布,263

and canary releasing, 263

和问题日志,423

and issue log, 423

和非功能性需求,225

and nonfunctional requirements, 225

和组织成熟度,420

and organizational maturity, 420

417 , 429–432 , 442的管理

management of, 417, 429–432, 442

部署,278

of deployment, 278

发展,430–431

of development, 430–431

发行量, 4–11 , 279

of releases, 4–11, 279

减少:

reducing:

通过持续交付,279

through continuous delivery, 279

通过持续部署,267

through continuous deployment, 267

通过回顾,431

through retrospectives, 431

通过虚拟化,303

through virtualization, 303

角色,424

Roles, 424

回滚

Roll back

和人工制品,373

and artifacts, 373

和遗留系统,252

and legacy systems, 252

自动化, 10

automating, 10

频繁且糟糕的配置管理,436

frequent, and poor configuration management, 436

数据库, 328 , 331–334

of databases, 328, 331–334

降低释放风险,109

reducing risk of releasing with, 109

132 , 259–265的策略

strategies of, 132, 259–265

与紧急修复,266

vs. emergency fixes, 266

前滚数据库,328

Roll forward of databases, 328

滚动构建,65

Rolling builds, 65

根本原因分析,433

Root cause analysis, 433

路由器,263

Routers, 263

和蓝绿部署,261

and blue-green deployments, 261

配置管理,300

configuration management of, 300

路径,305

rPath, 305

转数, 294 , 299

RPM, 294, 299

RSA, 273

RSA, 273

同步, 156 , 162

Rsync, 156, 162

红宝石, 155 , 283

Ruby, 155, 283

红宝石,355

Ruby Gems, 355

Rails上的 Ruby 328,354

Ruby on Rails, 328, 354

RubyGems, 38 , 151 , 294

RubyGems, 38, 151, 294

运行时优化,245

Runtime optimisation, 245

小号

S

悲伤的道路,88

Sad path, 88

萨希, 134 , 197

Sahi, 134, 197

销售人员,313

SalesForce, 313

圣, 374

SAN, 374

萨班斯-奥克斯利法案。参见 SOX

Sarbanes-Oxley. See SOX

可扩展性测试,231

Scalability testing, 231

缩放

Scaling

用于容量测试,236

for capacity testing, 236

通过云计算,313

through cloud computing, 313

SCCS(源代码控制系统32,382

SCCS (Source Code Control System), 32, 382

场景,容量测试,238

Scenarios, in capacity testing, 238

SCons,147

SCons, 147

SCP, 162

Scp, 162

屏幕录制, 136 , 213–214

Screen recording, 136, 213–214

脚本和部署管道,152

Scripting and the deployment pipeline, 152

敏捷, 422 , 427

Scrum, 422, 427

接缝,350

Seams, 350

安全

Security

和云计算,313

and cloud computing, 313

和配置管理,43

and configuration management, 43

和监控,322

and monitoring, 322

和网络路由,303

and network routing, 303

作为非功能性需求,423

as a nonfunctional requirement, 423

作为测试策略的一部分,91

as part of a testing strategy, 91

洞, 131

holes in, 131

基础设施,285–286

of infrastructure, 285–286

硒,197

Selenium, 197

硒网221,310

Selenium Grid, 221, 310

硒远程处理,221

Selenium Remoting, 221

自助服务部署112,255

Self-service deployments, 112, 255

高级负责人,422

Senior responsible owner, 422

服务资产和配置管理,421

Service asset and configuration management, 421

服务连续性规划,282

Service continuity planning, 282

服务设计,421

Service design, 421

服务中断,286

Service disruptions, 286

服务运营,421

Service operation, 421

服务包,290

Service packs, 290

服务测试和验证,421

Service testing and validation, 421

服务转换,421

Service transition, 421

服务等级协定。 服务水平协议

Service-level agreements. See SLA

面向服务的架构

Service-oriented architectures

和数据库,329

and databases, 329

和部署, 156 , 258

and deployment, 156, 258

和环境,278

and environments, 278

容量测试, 239 , 241

capacity testing, 239, 241

推广, 257

promoting, 257

SETI@家, 313

SETI@Home, 313

严重性,430

Severity, 430

服务连续性规划,423

Sevice continuity planning, 423

阴影域。请参阅 蓝绿部署

Shadow domains. See Blue-green deployments

共享文件系统作为工件存储库,375

Shared filesystems as artifact repositories, 375

共享图书馆,352

Shared library, 352

共享资源,261

Shared resources, 261

共同理解,423

Shared understanding, 423

共享架构264,313

Shared-nothing architectures, 264, 313

陈列柜, 128 , 426

Showcases, 128, 426

作为手动测试的一种形式,90

as a form of manual testing, 90

作为风险缓解策略,433

as a risk mitigation strategy, 433

沙特尔沃思,马克,394

Shuttleworth, Mark, 394

并行部署,262

Side-by-side deployment, 262

筒仓

Silos

和组件,358

and components, 358

和部署,8

and deployment, 8

开发和运营,279

development and operations, 279

管理交付,439–440

managing delivery, 439–440

猿猴,74 岁

Simian, 74

简单性和非功能性需求,229

Simplicity and nonfunctional requirements, 229

容量测试模拟,239

Simulation for capacity testing, 239

Skype,75 岁

Skype, 75

SLA服务级别协议128、251、280、314、331 _ _

SLA (service-level agreements), 128, 251, 280, 314, 331

慢速测试

Slow tests

构建失败,73

failing the build, 73

单元测试和测试替身,89

unit tests and test doubles, 89

冒烟测试

Smoke tests

和行为驱动的监控,323

and behavior-driven monitoring, 323

和基础设施管理,301

and infrastructure management, 301

和遗留系统,95

and legacy systems, 95

和编排,258

and orchestration, 258

作为...的一部分:

as part of:

验收测试套件,217

acceptance test suite, 217

集成管道,361

integration pipeline, 361

发布计划,251

release plan, 251

对于蓝绿部署,261

for blue-green deployments, 261

用于部署,273

for deployment, 273

对于部署脚本167、255

for deployment scripts, 167, 255

SMTP(简单邮件传输协议)285、300

SMTP (Simple Mail Transfer Protocol), 285, 300

快照

Snapshots

在 Maven 中,377

in Maven, 377

虚拟机数量,305

of virtual machines, 305

SNMP(简单网络管理协议302、319

SNMP (Simple Network Management Protocol), 302, 319

软件工程学院,227

Software Engineering Institute, 227

索拉里斯,284

Solaris, 284

源头控制。请参见 版本控制

Source control. See Version control

SOX(萨班斯-奥克斯利法案280,436

SOX (Sarbanes-Oxley), 280, 436

规格。 验收标准

Specifications. See Acceptance criteria

间谍,92

Spies, 92

另见 测试替身

See also Test doubles

尖刺, 382 , 425

Spikes, 382, 425

斯普伦克,318

Splunk, 318

SQLLite,336

SqlLite, 336

SSH, 162 , 302

Ssh, 162, 302

稳定性, 230 , 369

Stability, 230, 369

稳定阶段,347

Stabilization phase, 347

稳定患者129,286

Stabilizing the patient, 129, 286

暂存环境, 258–259 , 290

Staging environment, 258–259, 290

利益相关者,422

Stakeholders, 422

斯托曼,理查德,316

Stallman, Richard, 316

386、409

StarTeam, 386, 409

状态

State

在验收测试中,204–206

in acceptance tests, 204–206

在中间件中,298–299

in middleware, 298–299

在单元测试179、183

in unit tests, 179, 183

静态分析,331

Static analysis, 331

静态编译,353

Static compilation, 353

静态链接,357

Static linking, 357

静态视图,403

Static views, 403

停线,119–120

Stop the line, 119–120

存储过程,334

Stored procedures, 334

故事

Stories

和验收标准,195

and acceptance criteria, 195

验收测试85、99、188、193 _ _ _

and acceptance tests, 85, 99, 188, 193

和组件,358

and components, 358

和缺陷,101

and defects, 101

和遗留系统,95

and legacy systems, 95

和非功能性需求,227–228

and nonfunctional requirements, 227–228

和吞吐量,138

and throughput, 138

投资,93

INVEST, 93

策略模式,351

Strategy pattern, 351

流媒体视频,315

Streaming video, 315

存根, 92 , 178

Stubs, 92, 178

用于开发能力测试,244

for developing capacity tests, 244

另见 测试替身

See also Test doubles

颠覆32,383-385,397 _ _ _

Subversion, 32, 383–385, 397

太阳, 294 , 359

Sun, 294, 359

沉没成本, 300 , 349

Sunk cost, 300, 349

支持

Support

和数据归档,282

and data archiving, 282

作为...的一部分:

as part of:

发布计划,252

release plan, 252

发布策略,251

release strategy, 251

降低成本,419

reducing cost, 419

苏塞Linux,154

SuSE Linux, 154

把它扫到地毯下,351

Sweeping it under the rug, 351

符号链接, 260 , 269 , 271 , 294

Symbolic links, 260, 269, 271, 294

系统内部,301

Sysinternals, 301

系统中心配置管理291,296

System Center Configuration Manager, 291, 296

系统特性,226

System characteristics, 226

记录系统, 381 , 418

System of record, 381, 418

T

标记

Tagging

并发布,409

and releases, 409

在 ClearCase 中,404

in ClearCase, 404

在简历中,383

in CVS, 383

在颠覆中,384

in Subversion, 384

另见 版本控制实践

See also Version control practices

塔伦蒂诺,328 岁

Tarantino, 328

面向任务的构建工具,145

Task-oriented build tools, 145

TC3, 314

TC3, 314

TCP/IP, 300

TCP/IP, 300

TCP转储,301

Tcpdump, 301

TCP视图,301

TCPView, 301

团队基础服务器,386

Team Foundation Server, 386

团队规模

Team size

和验收测试,214

and acceptance testing, 214

和组件,357

and components, 357

持续交付规模如何?, 16

does continuous delivery scale?, 16

使用构建大师,174

using a build master, 174

另见 大型团队

See also Large teams

团队城市,58 岁

TeamCity, 58

技术债务330,406

Technical debt, 330, 406

模板, 305 , 309–310

Templates, 305, 309–310

临时329,332

Temporary tables, 329, 332

测试自动化金字塔,178

Test automation pyramid, 178

测试覆盖率87、121、174、435 _ _ _ _

Test coverage, 87, 121, 174, 435

测试数据

Test data

和数据库转储340,343

and database dumps, 340, 343

应用参考数据340,343

application reference data, 340, 343

与测试脱钩,336

decoupling from tests, 336

功能分区,337

functional partitioning, 337

在验收测试中,339–341

in acceptance tests, 339–341

在能力测试243、341–342

in capacity tests, 243, 341–342

在提交测试中,338–339

in commit tests, 338–339

管理,334–338

managing, 334–338

测试参考, 340 , 343

test reference, 340, 343

特定测试,340

test-specific, 340

测试双打, 89 , 91 , 178

Test doubles, 89, 91, 178

和验收测试,210-212

and acceptance tests, 210–212

和单元测试180–183,335

and unit tests, 180–183, 335

速度,89

speed of, 89

测试性能

Test performance

和数据库,335–336

and databases, 335–336

假装时间,184

faking time for, 184

通过虚拟化增加305,310

increasing through virtualization, 305, 310

测试排序,336

Test sequencing, 336

测试驱动开发, 71 , 178 , 427

Test-driven development, 71, 178, 427

另见 行为驱动开发

See also Behavior-driven development

测试人员,193

Testers, 193

测试象限图, 84 , 178

Testing quadrant diagram, 84, 178

测试策略

Testing strategies

作为开始的一部分,423

as part of inception, 423

绿地项目,92–94

greenfield projects, 92–94

的重要性,434

importance of, 434

遗留系统,95–96

legacy systems, 95–96

中期项目,94–95

midproject, 94–95

测试,105

Tests, 105

自适应, 336 , 338

adaptive, 336, 338

失败,308

failing, 308

隔离,336–337

isolation of, 336–337

手动, 126 , 128 , 138 , 189 , 223 , 343

manual, 126, 128, 138, 189, 223, 343

测序,336

sequencing, 336

安装和拆卸337,340

setup and tear down, 337, 340

类型, 84

types of, 84

另见 自动化测试手动测试

See also Automated tests, Manual testing

TFTP(普通文件传输协议),289

TFTP (Trivial File Transfer Protocol), 289

约束理论,138

Theory of Constraints, 138

线程池,318

Thread pools, 318

穿线

Threading

和应用性能,230

and application performance, 230

通过验收测试发现问题,189

catching problems with acceptance tests, 189

容量测试的阈值,238

Thresholds in capacity tests, 238

吞吐量, 225 , 231

Throughput, 225, 231

单元测试时间,184

Time in unit tests, 184

时间盒迭代,428

Time-boxed iterations, 428

超时和验收测试,207–210

Timeouts and acceptance testing, 207–210

趣伏里, 287 , 291 , 318

Tivoli, 287, 291, 318

待办事项,74

TODOs, 74

工具链

Toolchain

和测试环境,254

and testing environments, 254

和部署管道,114

and the deployment pipeline, 114

版本控制, 34 , 355

version controlling, 34, 355

托沃兹,莱纳斯385,395

Torvalds, Linus, 385, 395

触摸屏,204

Touch screen, 204

可追溯性

Traceability

和工件存储库,373

and artifact repository, 373

和依赖关系,363

and dependencies, 363

和部署管道,114

and the deployment pipeline, 114

和集成管道,362

and the integration pipeline, 362

从二进制文件到版本控制165–166、418

from binaries to version control, 165–166, 418

管理和执行,438–439

managing and enforcing, 438–439

流水线组件时,360366

when pipelining components, 360, 366

非功能性需求的权衡,227

Trade-offs for nonfunctional requirements, 227

红绿灯, 172 , 322

Traffic lights, 172, 322

管理测试状态的事务,337

Transactions for managing test state, 337

树干。参见 Mainline 开发

Trunk. See Mainline development

信任和依赖管理,369

Trust and dependency management, 369

元组,43

Tuple, 43

图灵完整性,198

Turing completeness, 198

扭曲, 85–86 , 191 , 196

Twist, 85–86, 191, 196

两阶段认证,273

Two-phase authentication, 273

ü

U

无处不在的语言,125

Ubiquitous language, 125

Ubuntu, 154 , 353 , 394

Ubuntu, 154, 353, 394

UI(用户界面)

UI (User Interface)

和容量测试,240-241

and capacity testing, 240–241

和单元测试,178–179

and unit testing, 178–179

另请参阅 图形用户界面

See also GUI

不受控制的变化, 20 , 265 , 273 , 288 , 290 , 306

Uncontrolled changes, 20, 265, 273, 288, 290, 306

无法部署的软件105,391

Undeployable software, 105, 391

联合文件系统,400

Union filesystem, 400

单元测试,89

Unit tests, 89

和异步,180

and asynchrony, 180

和CI,60

and CI, 60

数据库179-180,335-336

and databases, 179–180, 335–336

和依赖注入,179

and dependency injection, 179

和状态,183

and state, 183

和测试双打,180–183

and test doubles, 180–183

和用户界面,178–179

and UI, 178–179

作为提交阶段的一部分,120

as part of commit stage, 120

自动化, 135

automating, 135

假装时间,184

faking time for, 184

177-185的原则和实践

principles and practices of, 177–185

速度89 177 _

speed of, 89, 177

与验收测试,188

vs. acceptance tests, 188

另见 提交测试

See also Commit tests

升级, 261

Upgrading, 261

和部署脚本,153

and deployment scripting, 153

和用户安装的软件,267–270

and user-installed software, 267–270

作为...的一部分:

as part of:

发布计划,252

release plan, 252

发布策略,251

release strategy, 251

可用性

Usability

和非功能性需求,228

and nonfunctional requirements, 228

测试, 87 , 90 , 128 , 255

testing, 87, 90, 128, 255

用例和验收测试,86

Use cases and acceptance tests, 86

用户验收测试, 86 , 135

User acceptance testing, 86, 135

和测试数据,343

and test data, 343

在部署管道中,112

in the deployment pipeline, 112

用户安装的软件

User-installed software

和验收测试,125

and acceptance testing, 125

和金丝雀发布,264

and canary releasing, 264

和持续交付,267–270

and continuous delivery, 267–270

和部署自动化,129

and deployment automation, 129

崩溃报告,267–270

crash reports, 267–270

使用虚拟化进行测试,310

testing using virtualization, 310

升级,267–270

upgrading, 267–270

实用程序,421

Utility, 421

效用计算, 312 , 316

Utility computing, 312, 316

V

V

价值创造, 417 , 419 , 442

Value creation, 417, 419, 442

价值106–113、133、254、420 _ _ _ _

Value stream, 106–113, 133, 254, 420

速度, 139 , 431 , 433

Velocity, 139, 431, 433

供应商锁定315,317

Vendor lock-in, 315, 317

版本控制

Version control

和中间件配置,296 , 298 , 301

and middleware configuration, 296, 298, 301

作为持续交付的原则,25-26

as a principle of continuous delivery, 25–26

作为项目启动的一部分,424

as part of project initiation, 424

作为 CI 的先决条件,56–57

as prerequisite for CI, 56–57

定义,32

definition of, 32

分散式。请参见 分布式版本控制

distributed. See Distributed version control

对于数据库脚本,327

for database scripts, 327

对于图书馆, 38 , 354

for libraries, 38, 354

基于流的, 388 , 399–404

stream-based, 388, 399–404

版本控制实践

Version control practices

分枝。 分支

branching. See Branching

控制一切,33-35

control everything, 33–35

分叉,81

forking, 81

定期签到对于3659405的重要性

importance of regular check-ins for, 36, 59, 405

锁定, 383

locking, 383

主线。参见 Mainline 开发

mainline. See Mainline development

合并。 合并

merging. See Merging

基于流的开发,405

stream-based development, 405

观看次数, 334 , 403

Views, 334, 403

虚拟化

Virtualization

和蓝绿部署,262

and blue-green deployments, 262

和部署脚本,155

and deployment scripting, 155

和编排,258

and orchestration, 258

和配置服务器,303

and provisioning servers, 303

和部署管道304,307-310

and the deployment pipeline, 304, 307–310

基线, 53 , 305

baselines, 53, 305

303的定义

definition of, 303

用于验收测试, 217 , 220

for acceptance tests, 217, 220

用于创建测试环境,254

for creating testing environments, 254

用于环境管理,118

for environment management, 118

用于基础设施整合,304

for infrastructure consolidation, 304

用于管理遗留系统,306

for managing legacy systems, 306

用于加速测试305,310

for speeding up tests, 305, 310

用于测试非功能性需求,305

for testing nonfunctional requirements, 305

用于测试用户安装的软件,310

for testing user-installed software, 310

管理虚拟环境,305–307

managing virtual environments, 305–307

网络, 311

of networks, 311

通过303降低分娩风险

reducing risk of delivery through, 303

快照,305

Snapshot, 305

模板,305

templates for, 305

能见度, 4 , 113 , 362

Visibility, 4, 113, 362

视觉基础, 271 , 345

Visual Basic, 271, 345

视觉源安全,386

Visual SourceSafe, 386

可视140,366

Visualizations, 140, 366

Vnc2swf, 136 , 213

Vnc2swf, 136, 213

W

W

行走的骨架,134

Walking skeleton, 134

预热期, 245 , 259 , 261 , 272

Warm-up period, 245, 259, 261, 272

保修,421

Warranty, 421

战争,159

WARs, 159

废物, 105 , 391

Waste, 105, 391

网络服务器,296

Web servers, 296

网页服务

Web services

作为 API,357

as an API, 357

容量测试,241

capacity testing, 241

网络驱动程序, 134 , 197

WebDriver, 134, 197

网络逻辑,320

WebLogic, 320

WebSphere,153

WebSphere, 153

白色,197

White, 197

整个团队,124

Whole team, 124

和验收测试,125

and acceptance tests, 125

和交付,28

and delivery, 28

和部署,271

and deployment, 271

和提交阶段,172

and the commit stage, 172

维基百科,313

Wikipedia, 313

窗口驱动程序模式,201–204

Window driver pattern, 201–204

视窗, 154 , 310 , 352

Windows, 154, 310, 352

Windows 部署服务,288–290

Windows Deployment Services, 288–290

Windows 预安装环境,290

Windows Preinstallation Environment, 290

线鲨,301

Wireshark, 301

维克斯,283

WiX, 283

WordPress的,313

WordPress, 313

工作流程

Workflow

和分布式版本控制,396

and distributed version control, 396

和部署管道,111

and the deployment pipeline, 111

验收测试阶段,187

of acceptance testing stage, 187

工作软件, 56 , 425

Working software, 56, 425

艺术作品, 49 , 288–289 , 306

Works of art, 49, 288–289, 306

适用于我的机器综合症,116

Works on my machine syndrome, 116

工作区管理,62

Workspace management, 62

工作组, 291

WPKG, 291

管理员, 153

Wsadmin, 153

X

X

Xcopy 部署,353

Xcopy deployment, 353

XDoclet, 158

XDoclet, 158

XML(可扩展标记语言), 43 , 147 , 297

XML (Extensible Markup Language), 43, 147, 297

X单元, 135 , 191 , 200

XUnit, 135, 191, 200

Y

YAGNI(你不会需要它!),245

YAGNI (You ain’t gonna need it!), 245

山药,43

YAML, 43

百胜,294

Yum, 294

Z

Z

芝诺斯,318

Zenoss, 318

零缺陷,100

Zero defects, 100

零停机时间发布260–261,331–334

Zero-downtime releases, 260–261, 331–334

区域文件,300

zone files, 300

脚注

Footnotes

前言

Preface

1 . 实施精益软件开发,p。59.

1. Implementing Lean Software Development, p. 59.

第2章

Chapter 2

1 . 从技术上讲,配置信息可以被认为是一组元组。

1. Technically, configuration information can be thought of as a set of tuples.

第 4 章

Chapter 4

1 . 例如,James Shore [dsyXYv]。

1. For example, James Shore [dsyXYv].

2 . “探索性测试解释”,作者 James Bach [9BRHOz],p. 2.

2. “Exploratory Testing Explained” by James Bach [9BRHOz], p. 2.

3 . 第 5.7 节,第 136–140 页。

3. Section 5.7, pp. 136–140.

第 5 章

Chapter 5

1 . 在产品开发过程中基于客户反馈的迭代发现的重要性在Marty Cagan的启发和Steven Gary Blank的顿悟的四个步骤等书籍中得到了强调。

1. The importance of iterative discovery based on customer feedback in the product development process is emphasized in books like Inspired by Marty Cagan and The Four Steps to the Epiphany by Steven Gary Blank.

2 . Chris Read 提出了这个想法 [9EIHHS]。

2. Chris Read came up with this idea [9EIHHS].

3 . 埃文斯,2004 年。

3. Evans, 2004.

4 . 实施精益软件开发,p。59.

4. Implementing Lean Software Development, p. 59.

第6章

Chapter 6

1 . 在撰写本文时,Buildr 还可以无缝处理 Scala、Groovy 和 Ruby——在您阅读本文时,我们希望它支持更多以 JVM 为目标的语言。

1. Buildr also handles Scala, Groovy, and Ruby seamlessly at the time of writing—by the time you read this, we expect it to support more languages that target the JVM.

2 . Java 中的事情稍微复杂一些。在撰写本文时,Sun 的 Javac 编译器不执行增量构建(因此有 Ant 任务),但 IBM 的 Jikes 编译器可以。但是,Ant 中的javac任务将执行增量编译。

2. Things are slightly more complex in Java. At the time of writing, Sun’s Javac compiler did not do incremental builds (hence the Ant task), but IBM’s Jikes compiler could. However, the javac task in Ant will perform incremental compilation.

3 . 见埃文斯 (2003)。

3. See Evans (2003).

4 . 在本书的网站 [dzMeNE] 上有 Ant、Maven、MSBuild 和 Psake 的示例提交脚本。

4. There are example commit scripts for Ant, Maven, MSBuild, and Psake at the book’s website [dzMeNE].

5 . CPAN 是设计更好的平台打包系统之一,因为它可以以完全自动化的方式将 Perl 模块转换为 RedHat 或 Debian 包。如果所有的平台包格式都设计成允许自动转换为系统包格式,这种冲突就不会存在。

5. CPAN is one of the better-designed platform packaging systems, in that it is possible to convert a Perl module into a RedHat or Debian package in a fully automated way. If all platform package formats were designed to allow for automatic conversion to system package formats, this conflict would not exist.

6 . 与强制执行目录结构的 Rails 和 .NET 工具链不同,后者也会为您处理其中的一些问题。

6. Unlike Rails, which enforces the directory structure, and the .NET toolchain, which will also handle some of this for you.

7 . 查看 Jean-Paul Boodhoo 的博客条目 [ahdDZO]。

7. Check out Jean-Paul Boodhoo’s blog entry [ahdDZO].

第七章

Chapter 7

1 . James Carr 有一篇很好的博客文章,其中包含一些 TDD 模式 [cX6V1k]。

1. James Carr has a good blog entry with some TDD patterns [cX6V1k].

第8章

Chapter 8

1 . Bob Martin 阐述了为什么自动化验收测试很重要且不应外包的一些原因 [dB6JQ1]。

1. Bob Martin articulates some of the reasons for why automating acceptance testing is important and should not be outsourced [dB6JQ1].

2 . 这种方法的倡导者包括 JB Rainsberger,如他的“集成测试是一个骗局”博客文章 [a0tjh0] 和 James Shore,在他的“验收测试的问题”博客文章 [dsyXYv] 中所述。

2. Advocates of this approach include J. B. Rainsberger, as described in his “Integrated Tests Are a Scam” blog entry [a0tjh0], and James Shore, in his “The Problems with Acceptance Testing” blog entry [dsyXYv].

3 . 也就是说,它们必须是独立的、可协商的、有价值的、可估计的、小的和可测试的。

3. That is, they must be independent, negotiable, valuable, estimable, small, and testable.

4 . 在撰写本文时,Flex 就属于这一类——希望在您阅读本文时,新的测试框架已经如雨后春笋般涌现,可以通过 Flex 进行驱动测试。

4. At the time of writing, Flex fell into this category—hopefully by the time you read this, new testing frameworks will have sprung up to drive testing through Flex.

5 . Twist 是 Jez 的雇主创建的商业工具,它允许您直接在验收标准脚本上使用 Eclipse 的自动完成功能和参数查找,并允许您重构脚本和底层测试实现层,同时保持两者同步。

5. Twist, a commercial tool created by Jez’ employer, allows you to use Eclipse’s autocomplete functionality and parameter lookup on your acceptance criteria scripts directly, and allows you to refactor the scripts and the underlying test implementation layer while keeping the two synchronized.

6 . 尼加德, 2007, p. 115.

6. Nygard, 2007, p. 115.

第9章

Chapter 9

1 . 尼加德, 2007, p. 151.

1. Nygard, 2007, p. 151.

2 . 尼加德, 2007, p. 76.

2. Nygard, 2007, p. 76.

3 . 尼加德, 2007, p. 142.

3. Nygard, 2007, p. 142.

4 . 尼加德, 2007, p. 61.

4. Nygard, 2007, p. 61.

第10章

Chapter 10

1 . Unity 3D 网络播放器软件在其网站 [cFI7XI] 上发布统计数据。

1. The Unity 3D web player software publishes statistics on its site [cFI7XI].

2 . 有关亚马逊购物车演变的精彩分析,请查看 [blrMWp]。

2. For a great analysis of the evolution of Amazon’s shopping cart, check out [blrMWp].

3 . Google 为其所有内部服务创建了一个名为 Protocol Buffers 的框架,旨在处理版本控制 [beffuK]。

3. Google created a framework called Protocol Buffers for all its internal services, designed to handle versioning [beffuK].

第11章

Chapter 11

1 . 其中一些灵感来自 James White [9QRI77]。

1. Some of these are inspired by James White [9QRI77].

2 . 出于本章的目的,我们将支持视为运营工作的一部分,尽管情况并非总是如此。

2. For the purposes of this chapter, we will consider support to be part of the work of operations, although this is not always the case.

3 . 尼加德, 2007, p. 222.

3. Nygard, 2007, p. 222.

4 . 微软的 Azure 提供了一些服务,这些服务被视为云中的基础设施。然而,他们的虚拟机产品具有云平台的一些特征,因为在撰写本文时您没有 VM 的管理员访问权限,因此无法更改其配置或安装需要提升权限的软件。

4. Microsoft’s Azure provides some services that count as infrastructure in the cloud. However, their virtual machine offering has some of the characteristics of platforms in the cloud, since at the time of writing you do not have administrator access to VMs and thus cannot change their configuration or install software that requires elevated privileges.

第13章

Chapter 13

1 . 我们将在下一章“高级版本控制”中讨论分支策略。

1. We discuss branching strategies in the next chapter, “Advanced Version Control.”

2 . 这里有关于使用分布式版本控制系统的注意事项,我们将在下一章讨论。

2. There are caveats here on the use of distributed version control systems, which we’ll discuss in the next chapter.

3 . 在 Windows XP 中,免注册 COM 的引入允许应用程序将所需的 DLL 存储在自己的目录中。

3. In Windows XP, the introduction of registration-free COM allowed applications to store DLLs they require in their own directory.

4 . Melvin E. Conway,委员会如何发明,数据化 14:5:28–31。

4. Melvin E. Conway, How Do Committees Invent, Datamation 14:5:28–31.

5 . MacCormack、Rusnak、Baldwin,探索产品和组织架构之间的二元性:镜像假设的检验,哈佛商学院 [8XYofQ]。

5. MacCormack, Rusnak, Baldwin, Exploring the Duality between Product and Organizational Architectures: A Test of the Mirroring Hypothesis, Harvard Business School [8XYofQ].

6 . 本地存储库会定期从远程存储库更新——尽管可以将快照存储在远程存储库中,但这不是一个好主意。

6. Local repositories will periodically update from remote repositories—although it is possible to store snapshots in remote repositories, it is not a good idea.

第14章

Chapter 14

1 . 虽然开源系统和商业系统之间的区别对于您作为消费者的自由很重要,但值得注意的是,Subversion 是由商业组织 Collabnet 维护的,该组织提供付费支持。

1. While the distinction between open source and commercial systems is important for your freedoms as a consumer, it is worth noting that Subversion is maintained by a commercial organization, Collabnet, which provides paid support.

2 . 要幽默地了解主要的开源版本控制系统,请参阅 [bnb6MF]。

2. For a humorous look at the major open source version control systems, see [bnb6MF].

3 . RCS 与 SCCS 一样,仅适用于本地文件系统。

3. RCS, like SCCS, only works on local filesystems.

4 . 我们更喜欢更通用的术语“变更集”而不是“修订”,但 Subversion 专门使用“修订”。

4. We prefer the more general term “change set” to “revision,” but Subversion exclusively uses “revision.”

5 . 事实上,VSS 建议您在运行 VSS [c2M8mf] 时至少每周运行一次数据库完整性检查程序。

5. Indeed VSS recommends that you run a database integrity checker at least once a week when running VSS [c2M8mf].

6 . 摘自 Appleton 等人,1998 [dAI5I4]。

6. Taken from Appleton et al., 1998 [dAI5I4].

7 . 正如Henrik Kniberg [ctlRvc]在多个敏捷团队的版本控制中所述。

7. As described in Version Control for Multiple Agile Teams by Henrik Kniberg [ctlRvc].

第15章

Chapter 15

1 . 美国计算机学会,1984 年,第一卷。27 期 9。

1. ACM, 1984, vol. 27 issue 9.

2 . 正如我们上面所说,我们认为迭代应该限定在两周内,而不是四个星期。

2. As we say above, we believe iterations should be time-boxed to two weeks, not four.